Merge "Make list of SADs to query configurable"
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 0fa4087..dfe2101 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -207,62 +207,65 @@
     public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;
 
     /**
-     * Default of {@link #getPriority}.
+     * Default of {@link #getBias}.
      * @hide
      */
-    public static final int PRIORITY_DEFAULT = 0;
+    public static final int BIAS_DEFAULT = 0;
 
     /**
-     * Value of {@link #getPriority} for expedited syncs.
+     * Value of {@link #getBias} for expedited syncs.
      * @hide
      */
-    public static final int PRIORITY_SYNC_EXPEDITED = 10;
+    public static final int BIAS_SYNC_EXPEDITED = 10;
 
     /**
-     * Value of {@link #getPriority} for first time initialization syncs.
+     * Value of {@link #getBias} for first time initialization syncs.
      * @hide
      */
-    public static final int PRIORITY_SYNC_INITIALIZATION = 20;
+    public static final int BIAS_SYNC_INITIALIZATION = 20;
 
     /**
-     * Value of {@link #getPriority} for a BFGS app (overrides the supplied
-     * JobInfo priority if it is smaller).
+     * Value of {@link #getBias} for a BFGS app (overrides the supplied
+     * JobInfo bias if it is smaller).
      * @hide
      */
-    public static final int PRIORITY_BOUND_FOREGROUND_SERVICE = 30;
+    public static final int BIAS_BOUND_FOREGROUND_SERVICE = 30;
 
     /** @hide For backward compatibility. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int PRIORITY_FOREGROUND_APP = PRIORITY_BOUND_FOREGROUND_SERVICE;
+    public static final int PRIORITY_FOREGROUND_APP = BIAS_BOUND_FOREGROUND_SERVICE;
 
     /**
-     * Value of {@link #getPriority} for a FG service app (overrides the supplied
-     * JobInfo priority if it is smaller).
+     * Value of {@link #getBias} for a FG service app (overrides the supplied
+     * JobInfo bias if it is smaller).
      * @hide
      */
+    public static final int BIAS_FOREGROUND_SERVICE = 35;
+
+    /** @hide For backward compatibility. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int PRIORITY_FOREGROUND_SERVICE = 35;
+    public static final int PRIORITY_FOREGROUND_SERVICE = BIAS_FOREGROUND_SERVICE;
 
     /**
-     * Value of {@link #getPriority} for the current top app (overrides the supplied
-     * JobInfo priority if it is smaller).
+     * Value of {@link #getBias} for the current top app (overrides the supplied
+     * JobInfo bias if it is smaller).
      * @hide
      */
-    public static final int PRIORITY_TOP_APP = 40;
+    public static final int BIAS_TOP_APP = 40;
 
     /**
-     * Adjustment of {@link #getPriority} if the app has often (50% or more of the time)
+     * Adjustment of {@link #getBias} if the app has often (50% or more of the time)
      * been running jobs.
      * @hide
      */
-    public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
+    public static final int BIAS_ADJ_OFTEN_RUNNING = -40;
 
     /**
-     * Adjustment of {@link #getPriority} if the app has always (90% or more of the time)
+     * Adjustment of {@link #getBias} if the app has always (90% or more of the time)
      * been running jobs.
      * @hide
      */
-    public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
+    public static final int BIAS_ADJ_ALWAYS_RUNNING = -80;
 
     /**
      * Indicates that the implementation of this job will be using
@@ -355,7 +358,7 @@
     private final long flexMillis;
     private final long initialBackoffMillis;
     private final int backoffPolicy;
-    private final int priority;
+    private final int mBias;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int flags;
 
@@ -403,8 +406,8 @@
     }
 
     /** @hide */
-    public int getPriority() {
-        return priority;
+    public int getBias() {
+        return mBias;
     }
 
     /** @hide */
@@ -740,7 +743,7 @@
         if (backoffPolicy != j.backoffPolicy) {
             return false;
         }
-        if (priority != j.priority) {
+        if (mBias != j.mBias) {
             return false;
         }
         if (flags != j.flags) {
@@ -787,7 +790,7 @@
         hashCode = 31 * hashCode + Long.hashCode(flexMillis);
         hashCode = 31 * hashCode + Long.hashCode(initialBackoffMillis);
         hashCode = 31 * hashCode + backoffPolicy;
-        hashCode = 31 * hashCode + priority;
+        hashCode = 31 * hashCode + mBias;
         hashCode = 31 * hashCode + flags;
         return hashCode;
     }
@@ -826,7 +829,7 @@
         backoffPolicy = in.readInt();
         hasEarlyConstraint = in.readInt() == 1;
         hasLateConstraint = in.readInt() == 1;
-        priority = in.readInt();
+        mBias = in.readInt();
         flags = in.readInt();
     }
 
@@ -857,7 +860,7 @@
         backoffPolicy = b.mBackoffPolicy;
         hasEarlyConstraint = b.mHasEarlyConstraint;
         hasLateConstraint = b.mHasLateConstraint;
-        priority = b.mPriority;
+        mBias = b.mBias;
         flags = b.mFlags;
     }
 
@@ -902,7 +905,7 @@
         out.writeInt(backoffPolicy);
         out.writeInt(hasEarlyConstraint ? 1 : 0);
         out.writeInt(hasLateConstraint ? 1 : 0);
-        out.writeInt(priority);
+        out.writeInt(mBias);
         out.writeInt(this.flags);
     }
 
@@ -1020,7 +1023,7 @@
         private Bundle mTransientExtras = Bundle.EMPTY;
         private ClipData mClipData;
         private int mClipGrantFlags;
-        private int mPriority = PRIORITY_DEFAULT;
+        private int mBias = BIAS_DEFAULT;
         private int mFlags;
         // Requirements.
         private int mConstraintFlags;
@@ -1074,7 +1077,7 @@
             mTransientExtras = job.getTransientExtras();
             mClipData = job.getClipData();
             mClipGrantFlags = job.getClipGrantFlags();
-            mPriority = job.getPriority();
+            mBias = job.getBias();
             mFlags = job.getFlags();
             mConstraintFlags = job.getConstraintFlags();
             mNetworkRequest = job.getRequiredNetwork();
@@ -1100,9 +1103,17 @@
         }
 
         /** @hide */
+        @NonNull
+        public Builder setBias(int bias) {
+            mBias = bias;
+            return this;
+        }
+
+        /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public Builder setPriority(int priority) {
-            mPriority = priority;
+            // No-op for invalid calls. This wasn't a supported API before Tiramisu, so anyone
+            // calling this that isn't targeting T isn't guaranteed a behavior change.
             return this;
         }
 
@@ -1829,27 +1840,27 @@
     }
 
     /**
-     * Convert a priority integer into a human readable string for debugging.
+     * Convert a bias integer into a human readable string for debugging.
      * @hide
      */
-    public static String getPriorityString(int priority) {
-        switch (priority) {
-            case PRIORITY_DEFAULT:
-                return PRIORITY_DEFAULT + " [DEFAULT]";
-            case PRIORITY_SYNC_EXPEDITED:
-                return PRIORITY_SYNC_EXPEDITED + " [SYNC_EXPEDITED]";
-            case PRIORITY_SYNC_INITIALIZATION:
-                return PRIORITY_SYNC_INITIALIZATION + " [SYNC_INITIALIZATION]";
-            case PRIORITY_BOUND_FOREGROUND_SERVICE:
-                return PRIORITY_BOUND_FOREGROUND_SERVICE + " [BFGS_APP]";
-            case PRIORITY_FOREGROUND_SERVICE:
-                return PRIORITY_FOREGROUND_SERVICE + " [FGS_APP]";
-            case PRIORITY_TOP_APP:
-                return PRIORITY_TOP_APP + " [TOP_APP]";
+    public static String getBiasString(int bias) {
+        switch (bias) {
+            case BIAS_DEFAULT:
+                return BIAS_DEFAULT + " [DEFAULT]";
+            case BIAS_SYNC_EXPEDITED:
+                return BIAS_SYNC_EXPEDITED + " [SYNC_EXPEDITED]";
+            case BIAS_SYNC_INITIALIZATION:
+                return BIAS_SYNC_INITIALIZATION + " [SYNC_INITIALIZATION]";
+            case BIAS_BOUND_FOREGROUND_SERVICE:
+                return BIAS_BOUND_FOREGROUND_SERVICE + " [BFGS_APP]";
+            case BIAS_FOREGROUND_SERVICE:
+                return BIAS_FOREGROUND_SERVICE + " [FGS_APP]";
+            case BIAS_TOP_APP:
+                return BIAS_TOP_APP + " [TOP_APP]";
 
-                // PRIORITY_ADJ_* are adjustments and not used as real priorities.
+                // BIAS_ADJ_* are adjustments and not used as real priorities.
                 // No need to convert to strings.
         }
-        return priority + " [UNKNOWN]";
+        return bias + " [UNKNOWN]";
     }
 }
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 143c0f1..18f63b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -86,7 +86,7 @@
 
     /**
      * Set of possible execution types that a job can have. The actual type(s) of a job are based
-     * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before
+     * on the {@link JobStatus#lastEvaluatedBias}, which is typically evaluated right before
      * execution (when we're trying to determine which jobs to run next) and won't change after the
      * job has started executing.
      *
@@ -527,9 +527,8 @@
 
     /**
      * Takes jobs from pending queue and runs them on available contexts.
-     * If no contexts are available, preempts lower priority jobs to
-     * run higher priority ones.
-     * Lock on mJobs before calling this function.
+     * If no contexts are available, preempts lower bias jobs to run higher bias ones.
+     * Lock on mLock before calling this function.
      */
     @GuardedBy("mLock")
     void assignJobsToContextsLocked() {
@@ -596,9 +595,9 @@
             }
 
             // Find an available slot for nextPending. The context should be available OR
-            // it should have lowest priority among all running jobs
+            // it should have the lowest bias among all running jobs
             // (sharing the same Uid as nextPending)
-            int minPriorityForPreemption = Integer.MAX_VALUE;
+            int minBiasForPreemption = Integer.MAX_VALUE;
             int selectedContextId = -1;
             int allWorkTypes = getJobWorkTypes(nextPending);
             int workType = mWorkCountTracker.canJobStart(allWorkTypes);
@@ -631,7 +630,7 @@
                     // Maybe stop the job if it has had its day in the sun. Don't let a different
                     // app preempt jobs started for TOP apps though.
                     final String reason = shouldStopJobReason[j];
-                    if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
+                    if (job.lastEvaluatedBias < JobInfo.BIAS_TOP_APP
                             && reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
                             activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
                         // Right now, the way the code is set up, we don't need to explicitly
@@ -643,19 +642,19 @@
                     continue;
                 }
 
-                final int jobPriority = mService.evaluateJobPriorityLocked(job);
-                if (jobPriority >= nextPending.lastEvaluatedPriority) {
+                final int jobBias = mService.evaluateJobBiasLocked(job);
+                if (jobBias >= nextPending.lastEvaluatedBias) {
                     continue;
                 }
 
-                if (minPriorityForPreemption > jobPriority) {
+                if (minBiasForPreemption > jobBias) {
                     // Step down the preemption threshold - wind up replacing
-                    // the lowest-priority running job
-                    minPriorityForPreemption = jobPriority;
+                    // the lowest-bias running job
+                    minBiasForPreemption = jobBias;
                     selectedContextId = j;
-                    preemptReason = "higher priority job found";
+                    preemptReason = "higher bias job found";
                     preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
-                    // In this case, we're just going to preempt a low priority job, we're not
+                    // In this case, we're just going to preempt a low bias job, we're not
                     // actually starting a job, so don't set startingJob.
                 }
             }
@@ -749,7 +748,7 @@
                 continue;
             }
 
-            pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
+            pending.lastEvaluatedBias = mService.evaluateJobBiasLocked(pending);
 
             if (updateCounter) {
                 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
@@ -773,7 +772,7 @@
 
     @GuardedBy("mLock")
     private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
-        if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+        if (jobStatus.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
             // Don't restrict top apps' concurrency. The work type limits will make sure
             // background jobs have slots to run if the system has resources.
             return false;
@@ -866,9 +865,9 @@
             // Preemption case needs special care.
             updateNonRunningPrioritiesLocked(pendingJobs, false);
 
-            JobStatus highestPriorityJob = null;
-            int highPriWorkType = workType;
-            int highPriAllWorkTypes = workType;
+            JobStatus highestBiasJob = null;
+            int highBiasWorkType = workType;
+            int highBiasAllWorkTypes = workType;
             JobStatus backupJob = null;
             int backupWorkType = WORK_TYPE_NONE;
             int backupAllWorkTypes = WORK_TYPE_NONE;
@@ -893,40 +892,39 @@
                 }
 
                 // Only bypass the concurrent limit if we had preempted the job due to a higher
-                // priority job.
-                if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
+                // bias job.
+                if (nextPending.lastEvaluatedBias <= jobStatus.lastEvaluatedBias
                         && isPkgConcurrencyLimitedLocked(nextPending)) {
                     continue;
                 }
 
-                if (highestPriorityJob == null
-                        || highestPriorityJob.lastEvaluatedPriority
-                        < nextPending.lastEvaluatedPriority) {
-                    highestPriorityJob = nextPending;
+                if (highestBiasJob == null
+                        || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
+                    highestBiasJob = nextPending;
                 } else {
                     continue;
                 }
 
                 // In this path, we pre-empted an existing job. We don't fully care about the
-                // reserved slots. We should just run the highest priority job we can find,
+                // reserved slots. We should just run the highest bias job we can find,
                 // though it would be ideal to use an available WorkType slot instead of
                 // overloading slots.
-                highPriAllWorkTypes = getJobWorkTypes(nextPending);
-                final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
+                highBiasAllWorkTypes = getJobWorkTypes(nextPending);
+                final int workAsType = mWorkCountTracker.canJobStart(highBiasAllWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
                     // Just use the preempted job's work type since this new one is technically
                     // replacing it anyway.
-                    highPriWorkType = workType;
+                    highBiasWorkType = workType;
                 } else {
-                    highPriWorkType = workAsType;
+                    highBiasWorkType = workAsType;
                 }
             }
-            if (highestPriorityJob != null) {
+            if (highestBiasJob != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Running job " + jobStatus + " as preemption");
                 }
-                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
-                startJobLocked(worker, highestPriorityJob, highPriWorkType);
+                mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
+                startJobLocked(worker, highestBiasJob, highBiasWorkType);
             } else {
                 if (DEBUG) {
                     Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
@@ -944,11 +942,10 @@
             updateCounterConfigLocked();
             updateNonRunningPrioritiesLocked(pendingJobs, false);
 
-            // This slot is now free and we have pending jobs. Start the highest priority job we
-            // find.
-            JobStatus highestPriorityJob = null;
-            int highPriWorkType = workType;
-            int highPriAllWorkTypes = workType;
+            // This slot is now free and we have pending jobs. Start the highest bias job we find.
+            JobStatus highestBiasJob = null;
+            int highBiasWorkType = workType;
+            int highBiasAllWorkTypes = workType;
             for (int i = 0; i < pendingJobs.size(); i++) {
                 final JobStatus nextPending = pendingJobs.get(i);
 
@@ -965,23 +962,22 @@
                 if (workAsType == WORK_TYPE_NONE) {
                     continue;
                 }
-                if (highestPriorityJob == null
-                        || highestPriorityJob.lastEvaluatedPriority
-                        < nextPending.lastEvaluatedPriority) {
-                    highestPriorityJob = nextPending;
-                    highPriWorkType = workAsType;
-                    highPriAllWorkTypes = allWorkTypes;
+                if (highestBiasJob == null
+                        || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
+                    highestBiasJob = nextPending;
+                    highBiasWorkType = workAsType;
+                    highBiasAllWorkTypes = allWorkTypes;
                 }
             }
 
-            if (highestPriorityJob != null) {
+            if (highestBiasJob != null) {
                 // This slot is free, and we haven't yet hit the limit on
                 // concurrent jobs...  we can just throw the job in to here.
                 if (DEBUG) {
                     Slog.d(TAG, "About to run job: " + jobStatus);
                 }
-                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
-                startJobLocked(worker, highestPriorityJob, highPriWorkType);
+                mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
+                startJobLocked(worker, highestBiasJob, highBiasWorkType);
             }
         }
 
@@ -1255,9 +1251,9 @@
         int classification = 0;
 
         if (shouldRunAsFgUserJob(js)) {
-            if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+            if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
                 classification |= WORK_TYPE_TOP;
-            } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) {
+            } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
                 classification |= WORK_TYPE_FGS;
             } else {
                 classification |= WORK_TYPE_BG;
@@ -1267,7 +1263,7 @@
                 classification |= WORK_TYPE_EJ;
             }
         } else {
-            if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE
+            if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
                     || js.shouldTreatAsExpeditedJob()) {
                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
             }
@@ -1630,7 +1626,7 @@
                 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
                     int wt = mNumActuallyReservedSlots.keyAt(i);
                     if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
-                        // Try to give this slot to the highest priority one within its limits.
+                        // Try to give this slot to the highest bias one within its limits.
                         int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
                                 + mNumPendingJobs.get(wt);
                         if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
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 8f7cda9..bdaaf39 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobPackageTracker.java
@@ -481,7 +481,7 @@
         final long now = sUptimeMillisClock.millis();
         job.madeActive = now;
         rebatchIfNeeded(now);
-        if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+        if (job.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
             mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
         } else {
             mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
@@ -492,7 +492,7 @@
 
     public void noteInactive(JobStatus job, int stopReason, String debugReason) {
         final long now = sUptimeMillisClock.millis();
-        if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+        if (job.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
             mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
                     stopReason);
         } else {
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 c460312..e4b1e3e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -321,9 +321,9 @@
     private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
 
     /**
-     * A mapping of which uids are currently in the foreground to their effective priority.
+     * A mapping of which uids are currently in the foreground to their effective bias.
      */
-    final SparseIntArray mUidPriorityOverride = new SparseIntArray();
+    final SparseIntArray mUidBiasOverride = new SparseIntArray();
 
     /**
      * Which uids are currently performing backups, so we shouldn't allow their jobs to run.
@@ -1431,27 +1431,26 @@
 
     void updateUidState(int uid, int procState) {
         synchronized (mLock) {
-            final int prevPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT);
+            final int prevBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
             if (procState == ActivityManager.PROCESS_STATE_TOP) {
                 // Only use this if we are exactly the top app.  All others can live
-                // with just the foreground priority.  This means that persistent processes
-                // can never be the top app priority...  that is fine.
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
+                // with just the foreground bias.  This means that persistent processes
+                // can never have the top app bias...  that is fine.
+                mUidBiasOverride.put(uid, JobInfo.BIAS_TOP_APP);
             } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE);
+                mUidBiasOverride.put(uid, JobInfo.BIAS_FOREGROUND_SERVICE);
             } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE);
+                mUidBiasOverride.put(uid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
             } else {
-                mUidPriorityOverride.delete(uid);
+                mUidBiasOverride.delete(uid);
             }
-            final int newPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT);
-            if (prevPriority != newPriority) {
+            final int newBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
+            if (prevBias != newBias) {
                 if (DEBUG) {
-                    Slog.d(TAG, "UID " + uid + " priority changed from " + prevPriority
-                            + " to " + newPriority);
+                    Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias);
                 }
                 for (int c = 0; c < mControllers.size(); ++c) {
-                    mControllers.get(c).onUidPriorityChangedLocked(uid, newPriority);
+                    mControllers.get(c).onUidBiasChangedLocked(uid, newBias);
                 }
             }
         }
@@ -2202,17 +2201,17 @@
 
     /**
      * Check if a job is restricted by any of the declared {@link JobRestriction}s.
-     * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not
+     * Note, that the jobs with {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher may not
      * be restricted, thus we won't even perform the check, but simply return null early.
      *
      * @param job to be checked
      * @return the first {@link JobRestriction} restricting the given job that has been found; null
-     * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP}
+     * - if passes all the restrictions or has {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias
      * or higher.
      */
     private JobRestriction checkIfRestricted(JobStatus job) {
-        if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) {
-            // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted
+        if (evaluateJobBiasLocked(job) >= JobInfo.BIAS_FOREGROUND_SERVICE) {
+            // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted
             return null;
         }
         for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
@@ -2683,28 +2682,28 @@
         reportActiveLocked();
     }
 
-    private int adjustJobPriority(int curPriority, JobStatus job) {
-        if (curPriority < JobInfo.PRIORITY_TOP_APP) {
+    private int adjustJobBias(int curBias, JobStatus job) {
+        if (curBias < JobInfo.BIAS_TOP_APP) {
             float factor = mJobPackageTracker.getLoadFactor(job);
             if (factor >= mConstants.HEAVY_USE_FACTOR) {
-                curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
+                curBias += JobInfo.BIAS_ADJ_ALWAYS_RUNNING;
             } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
-                curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
+                curBias += JobInfo.BIAS_ADJ_OFTEN_RUNNING;
             }
         }
-        return curPriority;
+        return curBias;
     }
 
-    int evaluateJobPriorityLocked(JobStatus job) {
-        int priority = job.getPriority();
-        if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) {
-            return adjustJobPriority(priority, job);
+    int evaluateJobBiasLocked(JobStatus job) {
+        int bias = job.getBias();
+        if (bias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
+            return adjustJobBias(bias, job);
         }
-        int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
+        int override = mUidBiasOverride.get(job.getSourceUid(), 0);
         if (override != 0) {
-            return adjustJobPriority(override, job);
+            return adjustJobBias(override, job);
         }
-        return adjustJobPriority(priority, job);
+        return adjustJobBias(bias, job);
     }
 
     final class LocalService implements JobSchedulerInternal {
@@ -3576,17 +3575,17 @@
             }
 
             boolean overridePrinted = false;
-            for (int i = 0; i < mUidPriorityOverride.size(); i++) {
-                int uid = mUidPriorityOverride.keyAt(i);
+            for (int i = 0; i < mUidBiasOverride.size(); i++) {
+                int uid = mUidBiasOverride.keyAt(i);
                 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
                     if (!overridePrinted) {
                         overridePrinted = true;
                         pw.println();
-                        pw.println("Uid priority overrides:");
+                        pw.println("Uid bias overrides:");
                         pw.increaseIndent();
                     }
                     pw.print(UserHandle.formatUid(uid));
-                    pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
+                    pw.print(": "); pw.println(mUidBiasOverride.valueAt(i));
                 }
             }
             if (overridePrinted) {
@@ -3657,9 +3656,9 @@
 
                 pw.increaseIndent();
                 job.dump(pw, false, nowElapsed);
-                int priority = evaluateJobPriorityLocked(job);
-                pw.print("Evaluated priority: ");
-                pw.println(JobInfo.getPriorityString(priority));
+                int bias = evaluateJobBiasLocked(job);
+                pw.print("Evaluated bias: ");
+                pw.println(JobInfo.getBiasString(bias));
 
                 pw.print("Tag: "); pw.println(job.getTag());
                 pw.print("Enq: ");
@@ -3693,8 +3692,8 @@
                     job.dump(pw, false, nowElapsed);
                     pw.decreaseIndent();
 
-                    pw.print("Evaluated priority: ");
-                    pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority));
+                    pw.print("Evaluated bias: ");
+                    pw.println(JobInfo.getBiasString(job.lastEvaluatedBias));
 
                     pw.print("Active at ");
                     TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
@@ -3832,13 +3831,13 @@
                 controller.dumpControllerStateLocked(
                         proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
             }
-            for (int i = 0; i < mUidPriorityOverride.size(); i++) {
-                int uid = mUidPriorityOverride.keyAt(i);
+            for (int i = 0; i < mUidBiasOverride.size(); i++) {
+                int uid = mUidBiasOverride.keyAt(i);
                 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
                     long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
                     proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
                     proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
-                            mUidPriorityOverride.valueAt(i));
+                            mUidBiasOverride.valueAt(i));
                     proto.end(pToken);
                 }
             }
@@ -3859,7 +3858,7 @@
 
                 job.writeToShortProto(proto, PendingJob.INFO);
                 job.dump(proto, PendingJob.DUMP, false, nowElapsed);
-                proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job));
+                proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobBiasLocked(job));
                 proto.write(PendingJob.PENDING_DURATION_MS, nowUptime - job.madePending);
 
                 proto.end(pjToken);
@@ -3892,7 +3891,7 @@
                     job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
 
                     proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
-                            evaluateJobPriorityLocked(job));
+                            evaluateJobBiasLocked(job));
 
                     proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
                             nowUptime - job.madeActive);
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 7c1e4c9f..b1ea14d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -530,7 +530,8 @@
             }
         }
 
-        /** Write out a tag with data comprising the required fields and priority of this job and
+        /**
+         * Write out a tag with data comprising the required fields and bias of this job and
          * its client.
          */
         private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
@@ -546,7 +547,7 @@
             }
             out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
             out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
-            out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
+            out.attribute(null, "bias", String.valueOf(jobStatus.getBias()));
             out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
             if (jobStatus.getInternalFlags() != 0) {
                 out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
@@ -819,15 +820,18 @@
             long lastFailedRunTime;
             int internalFlags = 0;
 
-            // Read out job identifier attributes and priority.
+            // Read out job identifier attributes and bias.
             try {
                 jobBuilder = buildBuilderFromXml(parser);
                 jobBuilder.setPersisted(true);
                 uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
 
-                String val = parser.getAttributeValue(null, "priority");
+                String val = parser.getAttributeValue(null, "bias");
+                if (val == null) {
+                    val = parser.getAttributeValue(null, "priority");
+                }
                 if (val != null) {
-                    jobBuilder.setPriority(Integer.parseInt(val));
+                    jobBuilder.setBias(Integer.parseInt(val));
                 }
                 val = parser.getAttributeValue(null, "flags");
                 if (val != null) {
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 607ed3c..524d68f 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
@@ -176,8 +176,8 @@
             }
             // Prioritize the top app. If neither are top apps, then use a later prioritization
             // check.
-            final int topPriority = prioritizeExistenceOver(JobInfo.PRIORITY_TOP_APP - 1,
-                    us1.basePriority, us2.basePriority);
+            final int topPriority = prioritizeExistenceOver(JobInfo.BIAS_TOP_APP - 1,
+                    us1.baseBias, us2.baseBias);
             if (topPriority != 0) {
                 return topPriority;
             }
@@ -190,8 +190,8 @@
             // They both have runnable EJs.
             // Prioritize an FGS+ app. If neither are FGS+ apps, then use a later prioritization
             // check.
-            final int fgsPriority = prioritizeExistenceOver(JobInfo.PRIORITY_FOREGROUND_SERVICE - 1,
-                    us1.basePriority, us2.basePriority);
+            final int fgsPriority = prioritizeExistenceOver(JobInfo.BIAS_FOREGROUND_SERVICE - 1,
+                    us1.baseBias, us2.baseBias);
             if (fgsPriority != 0) {
                 return fgsPriority;
             }
@@ -202,8 +202,8 @@
                 return 1;
             }
             // Order by any latent important proc states.
-            if (us1.basePriority != us2.basePriority) {
-                return us2.basePriority - us1.basePriority;
+            if (us1.baseBias != us2.baseBias) {
+                return us2.baseBias - us1.baseBias;
             }
             // Order by enqueue time.
             if (us1.earliestEnqueueTime < us2.earliestEnqueueTime) {
@@ -527,10 +527,10 @@
 
     @GuardedBy("mLock")
     @Override
-    public void onUidPriorityChangedLocked(int uid, int newPriority) {
+    public void onUidBiasChangedLocked(int uid, int newBias) {
         UidStats uidStats = mUidStats.get(uid);
-        if (uidStats != null && uidStats.basePriority != newPriority) {
-            uidStats.basePriority = newPriority;
+        if (uidStats != null && uidStats.baseBias != newBias) {
+            uidStats.baseBias = newBias;
             postAdjustCallbacks();
         }
     }
@@ -928,7 +928,7 @@
         final int unbypassableBlockedReasons;
         // TOP will probably have fewer reasons, so we may not have to worry about returning
         // BG_BLOCKED for a TOP app. However, better safe than sorry.
-        if (uidStats.basePriority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE
+        if (uidStats.baseBias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
                 || (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
             if (DEBUG) {
                 Slog.d(TAG, "Using FG bypass for " + jobStatus.getSourceUid());
@@ -1281,7 +1281,7 @@
 
     private static class UidStats {
         public final int uid;
-        public int basePriority;
+        public int baseBias;
         public final ArraySet<JobStatus> runningJobs = new ArraySet<>();
         public int numReadyWithConnectivity;
         public int numRequestedNetworkAvailable;
@@ -1298,7 +1298,7 @@
         private void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
             pw.print("UidStats{");
             pw.print("uid", uid);
-            pw.print("pri", basePriority);
+            pw.print("pri", baseBias);
             pw.print("#run", runningJobs.size());
             pw.print("#readyWithConn", numReadyWithConnectivity);
             pw.print("#netAvail", numRequestedNetworkAvailable);
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 b4651a9..dee716f 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
@@ -328,8 +328,8 @@
     public Network network;
     public ServiceInfo serviceInfo;
 
-    /** The evaluated priority of the job when it started running. */
-    public int lastEvaluatedPriority;
+    /** The evaluated bias of the job when it started running. */
+    public int lastEvaluatedBias;
 
     /**
      * Whether or not this particular JobStatus instance was treated as an EJ when it started
@@ -924,8 +924,8 @@
         return tag;
     }
 
-    public int getPriority() {
-        return job.getPriority();
+    public int getBias() {
+        return job.getBias();
     }
 
     public int getFlags() {
@@ -1947,9 +1947,9 @@
             if (job.isPersisted()) {
                 pw.println("PERSISTED");
             }
-            if (job.getPriority() != 0) {
-                pw.print("Priority: ");
-                pw.println(JobInfo.getPriorityString(job.getPriority()));
+            if (job.getBias() != 0) {
+                pw.print("Bias: ");
+                pw.println(JobInfo.getBiasString(job.getBias()));
             }
             if (job.getFlags() != 0) {
                 pw.print("Flags: ");
@@ -2217,7 +2217,7 @@
             proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
 
             proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
-            proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
+            proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getBias());
             proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
             proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
             // Foreground exemption can be determined from internal flags value.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 3fe8df2..5e73f42 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -133,11 +133,11 @@
     }
 
     /**
-     * Called when a UID's base priority has changed. The more positive the priority, the more
+     * Called when a UID's base bias has changed. The more positive the bias, the more
      * important the UID is.
      */
     @GuardedBy("mLock")
-    public void onUidPriorityChangedLocked(int uid, int newPriority) {
+    public void onUidBiasChangedLocked(int uid, int newBias) {
     }
 
     protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
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 5193179..3387b1d 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
@@ -29,8 +29,8 @@
  * should be scheduled or not based on the state of the system/device.
  * Every restriction is associated with exactly one stop reason, which could be retrieved using
  * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
- * Note, that this is not taken into account for the jobs that have priority
- * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
+ * Note, that this is not taken into account for the jobs that have
+ * {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
  */
 public abstract class JobRestriction {
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 1535fb0..0f0e928 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -100,7 +100,7 @@
     field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
     field public static final String INTERNET = "android.permission.INTERNET";
     field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
-    field public static final String LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK";
+    field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
     field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
     field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
     field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -4381,7 +4381,6 @@
     method @Nullable public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
     method public boolean getLockTaskMode();
-    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4395,7 +4394,6 @@
     method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public android.app.ActivityOptions setLockTaskEnabled(boolean);
-    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -31816,9 +31814,9 @@
     method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
     method public boolean isBatteryDischargePredictionPersonalized();
     method public boolean isDeviceIdleMode();
+    method public boolean isDeviceLightIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
-    method public boolean isLightDeviceIdleMode();
     method public boolean isPowerSaveMode();
     method public boolean isRebootingUserspaceSupported();
     method @Deprecated public boolean isScreenOn();
@@ -31829,7 +31827,7 @@
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
-    field public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+    field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -35366,7 +35364,7 @@
     field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
-    field public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK = "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
+    field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
     field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
     field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
     field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35407,8 +35405,8 @@
     field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
     field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
     field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
-    field public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
-    field public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
+    field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+    field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
     field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
     field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 368722e..a480cd9 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -332,6 +332,10 @@
 
 package android.provider {
 
+  public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static java.util.Map<java.lang.String,java.util.List<android.content.ContentValues>> queryRawContactEntity(@NonNull android.content.Context, long);
+  }
+
   public final class DeviceConfig {
     field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
     field public static final String NAMESPACE_APP_STANDBY = "app_standby";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 62cd9f7..786b787 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -26,7 +26,7 @@
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
     field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
     field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
-    field public static final String ALLOW_PLACE_IN_TWO_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS";
+    field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
     field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
@@ -687,11 +687,9 @@
   }
 
   public class BroadcastOptions {
-    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
     method public void setDontSendToRestrictedApps(boolean);
-    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
     method public android.os.Bundle toBundle();
@@ -6071,6 +6069,8 @@
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method public boolean hasUnusedFrontend(int);
+    method public boolean isLowestPriority(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
     method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 5f2c456..e08a493 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -158,10 +158,7 @@
         "android/util/LocalLog.java",
         // This should be android.util.IndentingPrintWriter, but it's not available in all branches.
         "com/android/internal/util/IndentingPrintWriter.java",
-        "com/android/internal/util/IState.java",
         "com/android/internal/util/MessageUtils.java",
-        "com/android/internal/util/State.java",
-        "com/android/internal/util/StateMachine.java",
         "com/android/internal/util/WakeupMessage.java",
     ],
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index c1b94787..860224c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -69,7 +69,7 @@
  * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
  * Context.startActivity(Intent, Bundle)} and related methods.
  */
-public class ActivityOptions extends ComponentOptions {
+public class ActivityOptions {
     private static final String TAG = "ActivityOptions";
 
     /**
@@ -168,6 +168,14 @@
     public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
 
     /**
+     * PendingIntent caller allows activity start even if PendingIntent creator is in background.
+     * This only works if the PendingIntent caller is allowed to start background activities,
+     * for example if it's in the foreground, or has BAL permission.
+     * @hide
+     */
+    public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
+            "android.pendingIntent.backgroundActivityAllowed";
+    /**
      * Callback for when the last frame of the animation is played.
      * @hide
      */
@@ -381,6 +389,12 @@
     /** @hide */
     public static final int ANIM_REMOTE_ANIMATION = 13;
 
+    /**
+     * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
+     * @hide
+     **/
+    public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
+
     private String mPackageName;
     private Rect mLaunchBounds;
     private int mAnimationType = ANIM_UNDEFINED;
@@ -432,6 +446,7 @@
     private String mSplashScreenThemeResName;
     @SplashScreen.SplashScreenStyle
     private int mSplashScreenStyle;
+    private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
     private boolean mRemoveWithTaskOrganizer;
     private boolean mLaunchedFromBubble;
     private boolean mTransientLaunch;
@@ -1081,12 +1096,13 @@
     }
 
     private ActivityOptions() {
-        super();
     }
 
     /** @hide */
     public ActivityOptions(Bundle opts) {
-        super(opts);
+        // If the remote side sent us bad parcelables, they won't get the
+        // results they want, which is their loss.
+        opts.setDefusable(true);
 
         mPackageName = opts.getString(KEY_PACKAGE_NAME);
         try {
@@ -1184,6 +1200,8 @@
         mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION);
         mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
         mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
+        mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                PENDING_INTENT_BAL_ALLOWED_DEFAULT);
         mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
         mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
         mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
@@ -1408,6 +1426,24 @@
     }
 
     /**
+     * Set PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     * @hide
+     */
+    public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+        mPendingIntentBalAllowed = allowed;
+    }
+
+    /**
+     * Get PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     * @hide
+     */
+    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        return mPendingIntentBalAllowed;
+    }
+
+    /**
      * Sets whether the activity is to be launched into LockTask mode.
      *
      * Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1832,9 +1868,8 @@
      * object; you must not modify it, but can supply it to the startActivity
      * methods that take an options Bundle.
      */
-    @Override
     public Bundle toBundle() {
-        Bundle b = super.toBundle();
+        Bundle b = new Bundle();
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
@@ -1981,6 +2016,7 @@
         if (mSplashScreenThemeResName != null && !mSplashScreenThemeResName.isEmpty()) {
             b.putString(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResName);
         }
+        b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
         if (mRemoveWithTaskOrganizer) {
             b.putBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER, mRemoveWithTaskOrganizer);
         }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 4ca3beb..4e19abf 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -34,7 +34,7 @@
  * {@hide}
  */
 @SystemApi
-public class BroadcastOptions extends ComponentOptions {
+public class BroadcastOptions {
     private long mTemporaryAppAllowlistDuration;
     private @TempAllowListType int mTemporaryAppAllowlistType;
     private @ReasonCode int mTemporaryAppAllowlistReasonCode;
@@ -43,6 +43,7 @@
     private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private boolean mDontSendToRestrictedApps = false;
     private boolean mAllowBackgroundActivityStarts;
+    private boolean mPendingIntentBalAllowed = ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
 
     /**
      * How long to temporarily put an app on the power allowlist when executing this broadcast
@@ -79,6 +80,16 @@
             "android:broadcast.dontSendToRestrictedApps";
 
     /**
+     * PendingIntent caller allows activity start even if PendingIntent creator is in background.
+     * This only works if the PendingIntent caller is allowed to start background activities,
+     * for example if it's in the foreground, or has BAL permission.
+     * TODO: Merge it with ActivityOptions.
+     * @hide
+     */
+    public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
+            "android.pendingIntent.backgroundActivityAllowed";
+
+    /**
      * Corresponds to {@link #setBackgroundActivityStartsAllowed}.
      */
     private static final String KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS =
@@ -108,14 +119,12 @@
     }
 
     private BroadcastOptions() {
-        super();
         resetTemporaryAppAllowlist();
     }
 
     /** @hide */
     @TestApi
     public BroadcastOptions(@NonNull Bundle opts) {
-        super(opts);
         // Match the logic in toBundle().
         if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) {
             mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION);
@@ -132,6 +141,8 @@
         mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false);
         mAllowBackgroundActivityStarts = opts.getBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                 false);
+        mPendingIntentBalAllowed = opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
     }
 
     /**
@@ -303,6 +314,26 @@
     }
 
     /**
+     * Set PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     * TODO: Merge it with ActivityOptions.
+     * @hide
+     */
+    public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+        mPendingIntentBalAllowed = allowed;
+    }
+
+    /**
+     * Get PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     * TODO: Merge it with ActivityOptions.
+     * @hide
+     */
+    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        return mPendingIntentBalAllowed;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -310,9 +341,8 @@
      * object; you must not modify it, but can supply it to the sendBroadcast
      * methods that take an options Bundle.
      */
-    @Override
     public Bundle toBundle() {
-        Bundle b = super.toBundle();
+        Bundle b = new Bundle();
         if (isTemporaryAppAllowlistSet()) {
             b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
             b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType);
@@ -331,6 +361,8 @@
         if (mAllowBackgroundActivityStarts) {
             b.putBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS, true);
         }
+        // TODO: Add API for BroadcastOptions and have a shared base class with ActivityOptions.
+        b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
deleted file mode 100644
index d50a73a..0000000
--- a/core/java/android/app/ComponentOptions.java
+++ /dev/null
@@ -1,76 +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 android.app;
-
-import android.os.Bundle;
-
-/**
- * @hide
- */
-public class ComponentOptions {
-
-    /**
-     * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
-     * @hide
-     **/
-    public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
-
-    /**
-     * PendingIntent caller allows activity start even if PendingIntent creator is in background.
-     * This only works if the PendingIntent caller is allowed to start background activities,
-     * for example if it's in the foreground, or has BAL permission.
-     * @hide
-     */
-    public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
-            "android.pendingIntent.backgroundActivityAllowed";
-
-    private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
-
-    ComponentOptions() {
-    }
-
-    ComponentOptions(Bundle opts) {
-        // If the remote side sent us bad parcelables, they won't get the
-        // results they want, which is their loss.
-        opts.setDefusable(true);
-        setPendingIntentBackgroundActivityLaunchAllowed(
-                opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
-                        PENDING_INTENT_BAL_ALLOWED_DEFAULT));
-    }
-
-    /**
-     * Set PendingIntent activity is allowed to be started in the background if the caller
-     * can start background activities.
-     */
-    public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
-        mPendingIntentBalAllowed = allowed;
-    }
-
-    /**
-     * Get PendingIntent activity is allowed to be started in the background if the caller
-     * can start background activities.
-     */
-    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
-        return mPendingIntentBalAllowed;
-    }
-
-    public Bundle toBundle() {
-        Bundle b = new Bundle();
-        b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
-        return b;
-    }
-}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 01885b2..95ba523 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -114,7 +114,6 @@
     NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
     ParceledListSlice getNotificationChannelGroups(String pkg);
     boolean onlyHasDefaultChannel(String pkg, int uid);
-    int getBlockedAppCount(int userId);
     boolean areChannelsBypassingDnd();
     int getAppsBypassingDndCount(int uid);
     ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
new file mode 100644
index 0000000..f560434
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -0,0 +1,4 @@
+rubinxu@google.com
+acjohnston@google.com
+pgrafov@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
new file mode 100644
index 0000000..fb00fe5
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -0,0 +1,2 @@
+file:WorkDeviceExperience_OWNERS
+file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 6acbef2..10a5f14 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,11 +1,5 @@
 # Bug component: 142675
 
-# Android Enterprise team
-rubinxu@google.com
-sandness@google.com
-alexkershaw@google.com
-pgrafov@google.com
+file:EnterprisePlatform_OWNERS
 
-# Emeritus
-yamasani@google.com
-eranm@google.com
+yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
new file mode 100644
index 0000000..dcacaa2
--- /dev/null
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -0,0 +1,5 @@
+work-device-experience+reviews@google.com
+scottjonathan@google.com
+arangelov@google.com
+kholoudm@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6e918bd..e968052 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1186,11 +1186,6 @@
         mAttributionSource = attributionSource;
     }
 
-    /** {@hide} */
-    public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) {
-        setAttributionSource(attributionSource);
-    }
-
     @Override
     public boolean equals(@Nullable Object o) {
         if (o instanceof BluetoothDevice) {
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 20152f3..b5df4db 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -62,15 +62,15 @@
     private static final String TAG = "BluetoothManager";
     private static final boolean DBG = false;
 
-    private final AttributionSource mAttributionSource;
+    private static AttributionSource sAttributionSource = null;
     private final BluetoothAdapter mAdapter;
 
     /**
      * @hide
      */
     public BluetoothManager(Context context) {
-        mAttributionSource = resolveAttributionSource(context);
-        mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
+        sAttributionSource = resolveAttributionSource(context);
+        mAdapter = BluetoothAdapter.createAdapter(sAttributionSource);
     }
 
     /** {@hide} */
@@ -79,6 +79,9 @@
         if (context != null) {
             res = context.getAttributionSource();
         }
+        else if (sAttributionSource != null) {
+            return sAttributionSource;
+        }
         if (res == null) {
             res = ActivityThread.currentAttributionSource();
         }
@@ -198,8 +201,8 @@
             IBluetoothGatt iGatt = managerService.getBluetoothGatt();
             if (iGatt == null) return devices;
             devices = Attributable.setAttributionSource(
-                    iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource),
-                    mAttributionSource);
+                    iGatt.getDevicesMatchingConnectionStates(states, sAttributionSource),
+                    sAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ed65af3..b214255 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -32,7 +32,6 @@
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
-import android.bluetooth.BluetoothDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -11669,16 +11668,6 @@
         if (fromProtectedComponent) {
             mLocalFlags |= LOCAL_FLAG_FROM_PROTECTED_COMPONENT;
         }
-
-        // Special attribution fix-up logic for any BluetoothDevice extras
-        // passed via Bluetooth intents
-        if (mAction != null && mAction.startsWith("android.bluetooth.")
-                && hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
-            final BluetoothDevice device = getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (device != null) {
-                device.prepareToEnterProcess(source);
-            }
-        }
     }
 
     /** @hide */
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7ac385b..46f1797 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -599,12 +599,6 @@
     void forceDexOpt(String packageName);
 
     /**
-     * Execute the background dexopt job immediately on packages in packageNames.
-     * If null, then execute on all packages.
-     */
-    boolean runBackgroundDexoptJob(in List<String> packageNames);
-
-    /**
      * Reconcile the information we have about the secondary dex files belonging to
      * {@code packagName} and the actual dex files. For all dex files that were
      * deleted, update the internal records and delete the generated oat files.
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index f5b2ac5..2b52e96 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -22,11 +22,9 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.Arrays;
 
 import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
 
 /**
  * AmbientDisplayConfiguration encapsulates reading access to the configuration of ambient display.
@@ -90,7 +88,12 @@
 
     /** {@hide} */
     public boolean tapSensorAvailable() {
-        return !TextUtils.isEmpty(tapSensorType());
+        for (String tapType : tapSensorTypeMapping()) {
+            if (!TextUtils.isEmpty(tapType)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /** {@hide} */
@@ -143,18 +146,18 @@
         return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType);
     }
 
-    /** {@hide} */
-    private String tapSensorType() {
-        return mContext.getResources().getString(R.string.config_dozeTapSensorType);
-    }
-
-    /** {@hide} */
-    public String tapSensorType(int posture) {
-        return getSensorFromPostureMapping(
-                mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping),
-                tapSensorType(),
-                posture
-        );
+    /** {@hide}
+     * May support multiple postures.
+     */
+    public String[] tapSensorTypeMapping() {
+        String[] postureMapping =
+                mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping);
+        if (ArrayUtils.isEmpty(postureMapping)) {
+            return new String[] {
+                    mContext.getResources().getString(R.string.config_dozeTapSensorType)
+            };
+        }
+        return postureMapping;
     }
 
     /** {@hide} */
@@ -253,20 +256,4 @@
     private boolean boolSetting(String name, int user, int def) {
         return Settings.Secure.getIntForUser(mContext.getContentResolver(), name, def, user) != 0;
     }
-
-    /** {@hide} */
-    public static String getSensorFromPostureMapping(
-            String[] postureMapping,
-            String defaultValue,
-            int posture) {
-        String sensorType = defaultValue;
-        if (postureMapping != null && posture < postureMapping.length) {
-            sensorType = postureMapping[posture];
-        } else {
-            Log.e(TAG, "Unsupported doze posture " + posture
-                  + " postureMapping=" + Arrays.toString(postureMapping));
-        }
-
-        return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType;
-    }
 }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 35a974b..9a2cd06 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -204,7 +204,7 @@
                 aidlEvent.status,
                 modelHandle, aidlEvent.captureAvailable, captureSession,
                 aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
-                audioFormat, aidlEvent.data);
+                audioFormat, aidlEvent.data, aidlEvent.recognitionStillActive);
     }
 
     public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 163e6f0..b0439d0 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1175,6 +1175,11 @@
         @UnsupportedAppUsage
         @NonNull
         public final byte[] data;
+        /**
+         * Is recognition still active after this event.
+         * @hide
+         */
+        public final boolean recognitionStillActive;
 
         /** @hide */
         @TestApi
@@ -1182,6 +1187,16 @@
         public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs,
                 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
+            this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+                    capturePreambleMs, triggerInData, captureFormat, data,
+                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+        }
+
+        /** @hide */
+        public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+                int captureSession, int captureDelayMs, int capturePreambleMs,
+                boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+                boolean recognitionStillActive) {
             this.status = status;
             this.soundModelHandle = soundModelHandle;
             this.captureAvailable = captureAvailable;
@@ -1191,6 +1206,7 @@
             this.triggerInData = triggerInData;
             this.captureFormat = requireNonNull(captureFormat);
             this.data = data != null ? data : new byte[0];
+            this.recognitionStillActive = recognitionStillActive;
         }
 
         /**
@@ -1266,8 +1282,10 @@
                         .build();
             }
             byte[] data = in.readBlob();
+            boolean recognitionStillActive = in.readBoolean();
             return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
-                    captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data);
+                    captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data,
+                    recognitionStillActive);
         }
 
         /** @hide */
@@ -1293,8 +1311,8 @@
                 dest.writeByte((byte)0);
             }
             dest.writeBlob(data);
+            dest.writeBoolean(recognitionStillActive);
         }
-
         @Override
         public int hashCode() {
             final int prime = 31;
@@ -1312,6 +1330,7 @@
             result = prime * result + Arrays.hashCode(data);
             result = prime * result + soundModelHandle;
             result = prime * result + status;
+            result = result + (recognitionStillActive ? 1289 : 1291);
             return result;
         }
 
@@ -1334,6 +1353,8 @@
                 return false;
             if (!Arrays.equals(data, other.data))
                 return false;
+            if (recognitionStillActive != other.recognitionStillActive)
+                return false;
             if (soundModelHandle != other.soundModelHandle)
                 return false;
             if (status != other.status)
@@ -1370,7 +1391,9 @@
                         (", encoding=" + captureFormat.getEncoding()))
                     + ((captureFormat == null) ? "" :
                         (", channelMask=" + captureFormat.getChannelMask()))
-                    + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", recognitionStillActive=" + recognitionStillActive
+                    + "]";
         }
     }
 
@@ -1673,11 +1696,21 @@
 
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
-               int captureSession, int captureDelayMs, int capturePreambleMs,
-               boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
-               @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
+                int captureSession, int captureDelayMs, int capturePreambleMs,
+                boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+                @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
+            this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+                    capturePreambleMs, triggerInData, captureFormat, data, keyphraseExtras,
+                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+        }
+
+        public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+                int captureSession, int captureDelayMs, int capturePreambleMs,
+                boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+                @Nullable KeyphraseRecognitionExtra[] keyphraseExtras,
+                boolean recognitionStillActive) {
             super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
-                  capturePreambleMs, triggerInData, captureFormat, data);
+                    capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive);
             this.keyphraseExtras =
                     keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
         }
@@ -1713,11 +1746,12 @@
                     .build();
             }
             byte[] data = in.readBlob();
+            boolean recognitionStillActive = in.readBoolean();
             KeyphraseRecognitionExtra[] keyphraseExtras =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
                     captureSession, captureDelayMs, capturePreambleMs, triggerInData,
-                    captureFormat, data, keyphraseExtras);
+                    captureFormat, data, keyphraseExtras, recognitionStillActive);
         }
 
         @Override
@@ -1738,6 +1772,7 @@
                 dest.writeByte((byte)0);
             }
             dest.writeBlob(data);
+            dest.writeBoolean(recognitionStillActive);
             dest.writeTypedArray(keyphraseExtras, flags);
         }
 
@@ -1782,7 +1817,9 @@
                         (", encoding=" + captureFormat.getEncoding()))
                     + ((captureFormat == null) ? "" :
                         (", channelMask=" + captureFormat.getChannelMask()))
-                    + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", recognitionStillActive=" + recognitionStillActive
+                    + "]";
         }
     }
 
@@ -1798,9 +1835,17 @@
                 boolean captureAvailable, int captureSession, int captureDelayMs,
                 int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
                 @Nullable byte[] data) {
-            super(status, soundModelHandle, captureAvailable, captureSession,
-                    captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
-                    data);
+            this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+                    capturePreambleMs, triggerInData, captureFormat, data,
+                    status == RECOGNITION_STATUS_GET_STATE_RESPONSE);
+        }
+
+        public GenericRecognitionEvent(int status, int soundModelHandle,
+                boolean captureAvailable, int captureSession, int captureDelayMs,
+                int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+                @Nullable byte[] data, boolean recognitionStillActive) {
+            super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
+                    capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<GenericRecognitionEvent> CREATOR
@@ -1818,7 +1863,8 @@
             RecognitionEvent event = RecognitionEvent.fromParcel(in);
             return new GenericRecognitionEvent(event.status, event.soundModelHandle,
                     event.captureAvailable, event.captureSession, event.captureDelayMs,
-                    event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data);
+                    event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data,
+                    event.recognitionStillActive);
         }
 
         @Override
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 2e41382..753f349 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -2094,14 +2095,14 @@
      * Returns true if the device is currently in light idle mode.  This happens when a device
      * has had its screen off for a short time, switching it into a batching mode where we
      * execute jobs, syncs, networking on a batching schedule.  You can monitor for changes to
-     * this state with {@link #ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED}.
+     * this state with {@link #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED}.
      *
-     * @return Returns true if currently in active light device idle mode, else false.  This is
+     * @return Returns true if currently in active device light idle mode, else false.  This is
      * when light idle mode restrictions are being actively applied; it will return false if the
      * device is in a long-term idle mode but currently running a maintenance window where
      * restrictions have been lifted.
      */
-    public boolean isLightDeviceIdleMode() {
+    public boolean isDeviceLightIdleMode() {
         try {
             return mService.isLightDeviceIdleMode();
         } catch (RemoteException e) {
@@ -2110,6 +2111,18 @@
     }
 
     /**
+     * @see #isDeviceLightIdleMode()
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S,
+            publicAlternatives = "Use {@link #isDeviceLightIdleMode()} instead.")
+    public boolean isLightDeviceIdleMode() {
+        return isDeviceLightIdleMode();
+    }
+
+    /**
      * Return whether the given application package name is on the device's power allowlist.
      * Apps can be placed on the allowlist through the settings UI invoked by
      * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2559,12 +2572,26 @@
             = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
 
     /**
-     * Intent that is broadcast when the state of {@link #isLightDeviceIdleMode()} changes.
+     * Intent that is broadcast when the state of {@link #isDeviceLightIdleMode()} changes.
      * This broadcast is only sent to registered receivers.
      */
+    @SuppressLint("ActionValue") // Need to do "LIGHT_DEVICE_IDLE..." for legacy reasons
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
-            = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+    public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED =
+            // Use the old string so we don't break legacy apps.
+            "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
+
+    /**
+     * @see #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED
+     * @deprecated
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
+            publicAlternatives = "Use {@link #ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED} instead")
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED =
+            ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
 
     /**
      * @hide Intent that is broadcast when the set of power save allowlist apps has changed.
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 4d46325..13b22d3 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -505,8 +505,8 @@
 
         final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
                 signature.hashingInfo);
-        final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
-                signature.signingInfo);
+        final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
+                signature.signingInfos);
 
         if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
             throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
@@ -520,7 +520,7 @@
         if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
             throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
         }
-        if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+        if (signingInfos.signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
             throw new IOException(
                     "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
         }
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
index 688e3e9..2044502 100644
--- a/core/java/android/os/incremental/V4Signature.java
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -28,6 +28,7 @@
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 
 /**
  * V4 signature fields.
@@ -74,7 +75,7 @@
     }
 
     /**
-     * V4 signature data.
+     * Signature data.
      */
     public static class SigningInfo {
         public final byte[] apkDigest;  // used to match with the corresponding APK
@@ -98,7 +99,13 @@
          * Constructs SigningInfo from byte array.
          */
         public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
-            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+            return fromByteBuffer(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN));
+        }
+
+        /**
+         * Constructs SigningInfo from byte buffer.
+         */
+        public static SigningInfo fromByteBuffer(ByteBuffer buffer) throws IOException {
             byte[] apkDigest = readBytes(buffer);
             byte[] certificate = readBytes(buffer);
             byte[] additionalData = readBytes(buffer);
@@ -110,6 +117,62 @@
         }
     }
 
+    /**
+     * Optional signature data block with ID.
+     */
+    public static class SigningInfoBlock {
+        public final int blockId;
+        public final byte[] signingInfo;
+
+        public SigningInfoBlock(int blockId, byte[] signingInfo) {
+            this.blockId = blockId;
+            this.signingInfo = signingInfo;
+        }
+
+        static SigningInfoBlock fromByteBuffer(ByteBuffer buffer) throws IOException {
+            int blockId = buffer.getInt();
+            byte[] signingInfo = readBytes(buffer);
+            return new SigningInfoBlock(blockId, signingInfo);
+        }
+    }
+
+    /**
+     * V4 signature data.
+     */
+    public static class SigningInfos {
+        // Default signature.
+        public final SigningInfo signingInfo;
+        // Additional signatures corresponding to extended V2/V3/V31 blocks.
+        public final SigningInfoBlock[] signingInfoBlocks;
+
+        public SigningInfos(SigningInfo signingInfo) {
+            this.signingInfo = signingInfo;
+            this.signingInfoBlocks = new SigningInfoBlock[0];
+        }
+
+        public SigningInfos(SigningInfo signingInfo, SigningInfoBlock... signingInfoBlocks) {
+            this.signingInfo = signingInfo;
+            this.signingInfoBlocks = signingInfoBlocks;
+        }
+
+        /**
+         * Constructs SigningInfos from byte array.
+         */
+        public static SigningInfos fromByteArray(byte[] bytes) throws IOException {
+            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+            SigningInfo signingInfo = SigningInfo.fromByteBuffer(buffer);
+            if (!buffer.hasRemaining()) {
+                return new SigningInfos(signingInfo);
+            }
+            ArrayList<SigningInfoBlock> signingInfoBlocks = new ArrayList<>(1);
+            while (buffer.hasRemaining()) {
+                signingInfoBlocks.add(SigningInfoBlock.fromByteBuffer(buffer));
+            }
+            return new SigningInfos(signingInfo,
+                    signingInfoBlocks.toArray(new SigningInfoBlock[signingInfoBlocks.size()]));
+        }
+    }
+
     public final int version; // Always 2 for now.
     /**
      * Raw byte array containing the IncFS hashing data.
@@ -118,11 +181,11 @@
     @Nullable public final byte[] hashingInfo;
 
     /**
-     * Raw byte array containing the V4 signature data.
+     * Raw byte array containing V4 signatures.
      * <p>Passed as-is to the kernel. Can be retrieved later.
-     * @see SigningInfo#fromByteArray(byte[])
+     * @see SigningInfos#fromByteArray(byte[])
      */
-    @Nullable public final byte[] signingInfo;
+    @Nullable public final byte[] signingInfos;
 
     /**
      * Construct a V4Signature from .idsig file.
@@ -185,10 +248,10 @@
         return this.version == SUPPORTED_VERSION;
     }
 
-    private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfo) {
+    private V4Signature(int version, @Nullable byte[] hashingInfo, @Nullable byte[] signingInfos) {
         this.version = version;
         this.hashingInfo = hashingInfo;
-        this.signingInfo = signingInfo;
+        this.signingInfos = signingInfos;
     }
 
     private static V4Signature readFrom(InputStream stream) throws IOException {
@@ -205,7 +268,7 @@
     private void writeTo(OutputStream stream) throws IOException {
         writeIntLE(stream, this.version);
         writeBytes(stream, this.hashingInfo);
-        writeBytes(stream, this.signingInfo);
+        writeBytes(stream, this.signingInfos);
     }
 
     // Utility methods.
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 5e66bff..d544915 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -37,6 +37,7 @@
 import android.content.ContextWrapper;
 import android.content.CursorEntityIterator;
 import android.content.Entity;
+import android.content.Entity.NamedContentValues;
 import android.content.EntityIterator;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -55,6 +56,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.Pair;
 import android.view.View;
 
@@ -64,7 +66,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -5129,6 +5133,8 @@
      */
     public final static class RawContactsEntity
             implements BaseColumns, DataColumns, RawContactsColumns {
+        private static final String TAG = "ContactsContract.RawContactsEntity";
+
         /**
          * This utility class cannot be instantiated
          */
@@ -5181,6 +5187,73 @@
          * <P>Type: INTEGER</P>
          */
         public static final String DATA_ID = "data_id";
+
+        /**
+         * Query raw contacts entity by a contact ID, which can potentially be a corp profile
+         * contact ID
+         *
+         * @param context A context to get the ContentResolver from
+         * @param contactId Contact ID, which can potentialy be a corp profile contact ID.
+         *
+         * @return A map from a mimetype to a List of the entity content values.
+         * {@hide}
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+        public static @NonNull Map<String, List<ContentValues>> queryRawContactEntity(
+                @NonNull Context context, long contactId) {
+            Uri uri = RawContactsEntity.CONTENT_URI;
+            long realContactId = contactId;
+
+            if (Contacts.isEnterpriseContactId(contactId)) {
+                uri = RawContactsEntity.CORP_CONTENT_URI;
+                realContactId = contactId - Contacts.ENTERPRISE_CONTACT_ID_BASE;
+            }
+            final Map<String, List<ContentValues>> contentValuesListMap =
+                    new HashMap<String, List<ContentValues>>();
+            // The resolver may return the entity iterator with no data. It is possible.
+            // e.g. If all the data in the contact of the given contact id are not exportable ones,
+            //      they are hidden from the view of this method, though contact id itself exists.
+            EntityIterator entityIterator = null;
+            try {
+                final String selection = Data.CONTACT_ID + "=?";
+                final String[] selectionArgs = new String[] {String.valueOf(realContactId)};
+
+                entityIterator = RawContacts.newEntityIterator(context.getContentResolver().query(
+                            uri, null, selection, selectionArgs, null));
+
+                if (entityIterator == null) {
+                    Log.e(TAG, "EntityIterator is null");
+                    return contentValuesListMap;
+                }
+
+                if (!entityIterator.hasNext()) {
+                    Log.w(TAG, "Data does not exist. contactId: " + realContactId);
+                    return contentValuesListMap;
+                }
+
+                while (entityIterator.hasNext()) {
+                    Entity entity = entityIterator.next();
+                    for (NamedContentValues namedContentValues : entity.getSubValues()) {
+                        ContentValues contentValues = namedContentValues.values;
+                        String key = contentValues.getAsString(Data.MIMETYPE);
+                        if (key != null) {
+                            List<ContentValues> contentValuesList = contentValuesListMap.get(key);
+                            if (contentValuesList == null) {
+                                contentValuesList = new ArrayList<ContentValues>();
+                                contentValuesListMap.put(key, contentValuesList);
+                            }
+                            contentValuesList.add(contentValues);
+                        }
+                    }
+                }
+            } finally {
+                if (entityIterator != null) {
+                    entityIterator.close();
+                }
+            }
+            return contentValuesListMap;
+        }
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 535d8b7..93061ee 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17657,40 +17657,42 @@
             "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
 
     /**
-     * Activity Action: For system or preinstalled apps to show their {@link Activity} in 2-pane
-     * mode in Settings app on large screen devices.
+     * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
+     * in Settings app on large screen devices.
      * <p>
-     *     Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI} must be included to
-     * specify the intent for the activity which will be displayed in 2-pane mode in Settings app.
+     *     Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
+     * specify the intent for the activity which will be embedded in Settings app.
      * It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
      *
-     *     Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY} must be included to
+     *     Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY} must be included to
      * specify a key that indicates the menu item which will be highlighted on settings home menu.
      * <p>
      * Output: Nothing.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK =
-            "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK";
+    public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY =
+            "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
 
     /**
-     * Activity Extra: Specify the intent for the {@link Activity} which will be displayed in 2-pane
-     * mode in Settings app. It's an intent URI string from
+     * Activity Extra: Specify the intent for the {@link Activity} which will be embedded in
+     * Settings app. It's an intent URI string from
      * {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
      * <p>
-     * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+     * This must be passed as an extra field to
+     * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
      */
-    public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI =
-            "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI";
+    public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI =
+            "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
 
     /**
      * Activity Extra: Specify a key that indicates the menu item which should be highlighted on
      * settings home menu.
      * <p>
-     * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}.
+     * This must be passed as an extra field to
+     * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
      */
-    public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY =
-            "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY";
+    public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY =
+            "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
 
     /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 3d18a89..c945954 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1699,7 +1699,7 @@
         private ArrayList<Notification.Action> mSmartActions;
         private ArrayList<CharSequence> mSmartReplies;
         private boolean mCanBubble;
-        private boolean mVisuallyInterruptive;
+        private boolean mIsTextChanged;
         private boolean mIsConversation;
         private ShortcutInfo mShortcutInfo;
         private @RankingAdjustment int mRankingAdjustment;
@@ -1736,7 +1736,7 @@
             out.writeTypedList(mSmartActions, flags);
             out.writeCharSequenceList(mSmartReplies);
             out.writeBoolean(mCanBubble);
-            out.writeBoolean(mVisuallyInterruptive);
+            out.writeBoolean(mIsTextChanged);
             out.writeBoolean(mIsConversation);
             out.writeParcelable(mShortcutInfo, flags);
             out.writeInt(mRankingAdjustment);
@@ -1774,7 +1774,7 @@
             mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
             mSmartReplies = in.readCharSequenceList();
             mCanBubble = in.readBoolean();
-            mVisuallyInterruptive = in.readBoolean();
+            mIsTextChanged = in.readBoolean();
             mIsConversation = in.readBoolean();
             mShortcutInfo = in.readParcelable(cl);
             mRankingAdjustment = in.readInt();
@@ -1977,8 +1977,8 @@
         }
 
         /** @hide */
-        public boolean visuallyInterruptive() {
-            return mVisuallyInterruptive;
+        public boolean isTextChanged() {
+            return mIsTextChanged;
         }
 
         /** @hide */
@@ -2033,7 +2033,7 @@
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
                 ArrayList<CharSequence> smartReplies, boolean canBubble,
-                boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo,
+                boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
                 int rankingAdjustment, boolean isBubble) {
             mKey = key;
             mRank = rank;
@@ -2055,7 +2055,7 @@
             mSmartActions = smartActions;
             mSmartReplies = smartReplies;
             mCanBubble = canBubble;
-            mVisuallyInterruptive = visuallyInterruptive;
+            mIsTextChanged = isTextChanged;
             mIsConversation = isConversation;
             mShortcutInfo = shortcutInfo;
             mRankingAdjustment = rankingAdjustment;
@@ -2096,7 +2096,7 @@
                     other.mSmartActions,
                     other.mSmartReplies,
                     other.mCanBubble,
-                    other.mVisuallyInterruptive,
+                    other.mIsTextChanged,
                     other.mIsConversation,
                     other.mShortcutInfo,
                     other.mRankingAdjustment,
@@ -2153,7 +2153,7 @@
                         == (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
                     && Objects.equals(mSmartReplies, other.mSmartReplies)
                     && Objects.equals(mCanBubble, other.mCanBubble)
-                    && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive)
+                    && Objects.equals(mIsTextChanged, other.mIsTextChanged)
                     && Objects.equals(mIsConversation, other.mIsConversation)
                     // Shortcutinfo doesn't have equals either; use id
                     &&  Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index dd4de0a..3028a6d 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -691,6 +691,11 @@
          * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+         *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PHONE_STATE}.
+         *
          */
         @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
         void onMessageWaitingIndicatorChanged(boolean mwi);
@@ -710,6 +715,11 @@
          * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+         *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PHONE_STATE}.
+         *
          */
         @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
         void onCallForwardingIndicatorChanged(boolean cfi);
@@ -868,6 +878,10 @@
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
          * @param callState {@link PreciseCallState}
          */
         @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -910,6 +924,10 @@
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
          * @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
          */
         @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -932,9 +950,9 @@
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
-         * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
-         * or the calling app has carrier privileges
-         * (see {@link TelephonyManager#hasCarrierPrivileges}).
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
          *
          * @param dataConnectionState {@link PreciseDataConnectionState}
          */
@@ -1063,6 +1081,10 @@
          * given subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PHONE_STATE}.
+         *
          * @param emergencyNumberList Map associating all active subscriptions on the device with
          *                            the list of emergency numbers originating from that
          *                            subscription.
@@ -1157,6 +1179,11 @@
          *              For example, it could be the current active opportunistic subscription
          *              in use, or the subscription user selected as default data subscription in
          *              DSDS mode.
+         *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PHONE_STATE}.
+         *
          */
         @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
         void onActiveDataSubscriptionIdChanged(int subId);
@@ -1225,6 +1252,11 @@
          * <p>Because registration failures are ephemeral, this callback is not sticky.
          * Registrants will not receive the most recent past value when registering.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
+         * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+         *
          * @param cellIdentity        the CellIdentity, which must include the globally unique
          *                            identifier
          *                            for the cell (for example, all components of the CGI or ECGI).
@@ -1308,6 +1340,10 @@
          * subscription ID. Otherwise, this callback applies to
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
          * @param callAttributes the call attributes
          */
         @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1324,6 +1360,11 @@
          * <p>Barring info is provided for all services applicable to the current camped/registered
          * cell, for the registered PLMN and current access class/access category.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
+         * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+         *
          * @param barringInfo for all services on the current cell.
          * @see android.telephony.BarringInfo
          */
@@ -1341,6 +1382,10 @@
         /**
          * Callback invoked when the current physical channel configuration has changed
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
          * @param configs List of the current {@link PhysicalChannelConfig}s
          */
         @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1357,6 +1402,10 @@
         /**
          * Callback invoked when the data enabled changes.
          *
+         * The calling app should have carrier privileges
+         * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+         * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+         *
          * @param enabled {@code true} if data is enabled, otherwise disabled.
          * @param reason  Reason for data enabled/disabled.
          *                See {@link TelephonyManager.DataEnabledReason}.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3d39fbe..2c89a15 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,9 +50,13 @@
             = "settings_use_new_backup_eligibility_rules";
     /** @hide */
     public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
-
     /** @hide */
     public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+    /**
+     * Support per app's language selection
+     * @hide
+     */
+    public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -76,11 +80,13 @@
         DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "false");
+        DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
     static {
         PERSISTENT_FLAGS = new HashSet<>();
+        PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
         PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
     }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 8e4e99e..7e65d61 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -70,8 +70,8 @@
      */
     public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 3;
 
-    private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
-    private static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;
+    static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
+    static final int APK_SIGNATURE_SCHEME_V31_BLOCK_ID = 0x1b93ad61;
 
     /**
      * Returns {@code true} if the provided APK contains an APK Signature Scheme V3 signature.
@@ -260,7 +260,8 @@
                     verityDigest, mApk.getChannel().size(), signatureInfo);
         }
 
-        return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests);
+        return new VerifiedSigner(result.first, result.second, verityRootHash, contentDigests,
+                blockId);
     }
 
     private Pair<X509Certificate[], ApkSigningBlockUtils.VerifiedProofOfRotation>
@@ -572,13 +573,18 @@
         // All these are verified if requested.
         public final Map<Integer, byte[]> contentDigests;
 
+        // ID of the signature block used to verify.
+        public final int blockId;
+
         public VerifiedSigner(X509Certificate[] certs,
                 ApkSigningBlockUtils.VerifiedProofOfRotation por,
-                byte[] verityRootHash, Map<Integer, byte[]> contentDigests) {
+                byte[] verityRootHash, Map<Integer, byte[]> contentDigests,
+                int blockId) {
             this.certs = certs;
             this.por = por;
             this.verityRootHash = verityRootHash;
             this.contentDigests = contentDigests;
+            this.blockId = blockId;
         }
 
     }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
index 844816c..49cd600 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV4Verifier.java
@@ -16,6 +16,7 @@
 
 package android.util.apk;
 
+import static android.util.apk.ApkSignatureSchemeV3Verifier.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaKeyAlgorithm;
 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
@@ -52,38 +53,60 @@
  * @hide for internal use only.
  */
 public class ApkSignatureSchemeV4Verifier {
+    static final int APK_SIGNATURE_SCHEME_DEFAULT = 0xffffffff;
+
     /**
-     * Extracts and verifies APK Signature Scheme v4 signatures of the provided APK and returns the
+     * Extracts and verifies APK Signature Scheme v4 signature of the provided APK and returns the
      * certificates associated with each signer.
      */
     public static VerifiedSigner extractCertificates(String apkFile)
             throws SignatureNotFoundException, SecurityException {
+        V4Signature signature = extractSignature(apkFile);
+        return verify(apkFile, signature, APK_SIGNATURE_SCHEME_DEFAULT);
+    }
+
+    /**
+     * Extracts APK Signature Scheme v4 signature of the provided APK.
+     */
+    public static V4Signature extractSignature(String apkFile) throws SignatureNotFoundException {
         final File apk = new File(apkFile);
         final byte[] signatureBytes = IncrementalManager.unsafeGetFileSignature(
                 apk.getAbsolutePath());
         if (signatureBytes == null || signatureBytes.length == 0) {
             throw new SignatureNotFoundException("Failed to obtain signature bytes from IncFS.");
         }
-
-        final V4Signature signature;
-        final V4Signature.HashingInfo hashingInfo;
-        final V4Signature.SigningInfo signingInfo;
         try {
-            signature = V4Signature.readFrom(signatureBytes);
-
+            final V4Signature signature = V4Signature.readFrom(signatureBytes);
             if (!signature.isVersionSupported()) {
                 throw new SecurityException(
                         "v4 signature version " + signature.version + " is not supported");
             }
+            return signature;
+        } catch (IOException e) {
+            throw new SignatureNotFoundException("Failed to read V4 signature.", e);
+        }
+    }
 
+    /**
+     * Verifies APK Signature Scheme v4 signature and returns the
+     * certificates associated with each signer.
+     */
+    public static VerifiedSigner verify(String apkFile, final V4Signature signature,
+            final int v3BlockId) throws SignatureNotFoundException, SecurityException {
+        final V4Signature.HashingInfo hashingInfo;
+        final V4Signature.SigningInfos signingInfos;
+        try {
             hashingInfo = V4Signature.HashingInfo.fromByteArray(signature.hashingInfo);
-            signingInfo = V4Signature.SigningInfo.fromByteArray(signature.signingInfo);
+            signingInfos = V4Signature.SigningInfos.fromByteArray(signature.signingInfos);
         } catch (IOException e) {
             throw new SignatureNotFoundException("Failed to read V4 signature.", e);
         }
 
+        final V4Signature.SigningInfo signingInfo = findSigningInfoForBlockId(signingInfos,
+                v3BlockId);
+
         // Verify signed data and extract certificates and apk digest.
-        final byte[] signedData = V4Signature.getSignedData(apk.length(), hashingInfo,
+        final byte[] signedData = V4Signature.getSignedData(new File(apkFile).length(), hashingInfo,
                 signingInfo);
         final Pair<Certificate, byte[]> result = verifySigner(signingInfo, signedData);
 
@@ -95,6 +118,28 @@
         return new VerifiedSigner(new Certificate[]{result.first}, result.second, contentDigests);
     }
 
+    private static V4Signature.SigningInfo findSigningInfoForBlockId(
+            final V4Signature.SigningInfos signingInfos, final int v3BlockId)
+            throws SignatureNotFoundException {
+        // Use default signingInfo for v3 block.
+        if (v3BlockId == APK_SIGNATURE_SCHEME_DEFAULT
+                || v3BlockId == APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
+            return signingInfos.signingInfo;
+        }
+        for (V4Signature.SigningInfoBlock signingInfoBlock : signingInfos.signingInfoBlocks) {
+            if (v3BlockId == signingInfoBlock.blockId) {
+                try {
+                    return V4Signature.SigningInfo.fromByteArray(signingInfoBlock.signingInfo);
+                } catch (IOException e) {
+                    throw new SecurityException(
+                            "Failed to read V4 signature block: " + signingInfoBlock.blockId, e);
+                }
+            }
+        }
+        throw new SecurityException(
+                "Failed to find V4 signature block corresponding to V3 blockId: " + v3BlockId);
+    }
+
     private static Pair<Certificate, byte[]> verifySigner(V4Signature.SigningInfo signingInfo,
             final byte[] signedData) throws SecurityException {
         if (!isSupportedSignatureAlgorithm(signingInfo.signatureAlgorithmId)) {
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 35c602a..13bd587 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -22,6 +22,7 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
 
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
@@ -31,6 +32,7 @@
 import android.content.pm.parsing.result.ParseResult;
 import android.os.Build;
 import android.os.Trace;
+import android.os.incremental.V4Signature;
 import android.util.jar.StrictJarFile;
 
 import com.android.internal.util.ArrayUtils;
@@ -189,45 +191,51 @@
             boolean verifyFull) throws SignatureNotFoundException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
         try {
-            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
-                    ApkSignatureSchemeV4Verifier.extractCertificates(apkPath);
-            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
-            Signature[] signerSigs = convertToSignatures(signerCerts);
+            final V4Signature v4Signature = ApkSignatureSchemeV4Verifier.extractSignature(apkPath);
+
             Signature[] pastSignerSigs = null;
 
-            if (verifyFull) {
-                Map<Integer, byte[]> nonstreamingDigests;
-                Certificate[][] nonstreamingCerts;
+            Map<Integer, byte[]> nonstreamingDigests;
+            Certificate[][] nonstreamingCerts;
 
-                try {
-                    // v4 is an add-on and requires v2 or v3 signature to validate against its
-                    // certificate and digest
-                    ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
-                            ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
-                    nonstreamingDigests = v3Signer.contentDigests;
-                    nonstreamingCerts = new Certificate[][]{v3Signer.certs};
-                    if (v3Signer.por != null) {
-                        // populate proof-of-rotation information
-                        pastSignerSigs = new Signature[v3Signer.por.certs.size()];
-                        for (int i = 0; i < pastSignerSigs.length; i++) {
-                            pastSignerSigs[i] = new Signature(
-                                    v3Signer.por.certs.get(i).getEncoded());
-                            pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
-                        }
-                    }
-                } catch (SignatureNotFoundException e) {
-                    try {
-                        ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
-                                ApkSignatureSchemeV2Verifier.verify(apkPath, false);
-                        nonstreamingDigests = v2Signer.contentDigests;
-                        nonstreamingCerts = v2Signer.certs;
-                    } catch (SignatureNotFoundException ee) {
-                        throw new SecurityException(
-                                "V4 verification failed to collect V2/V3 certificates from : "
-                                        + apkPath, ee);
+            int v3BlockId = APK_SIGNATURE_SCHEME_DEFAULT;
+
+            try {
+                // v4 is an add-on and requires v2 or v3 signature to validate against its
+                // certificate and digest
+                ApkSignatureSchemeV3Verifier.VerifiedSigner v3Signer =
+                        ApkSignatureSchemeV3Verifier.unsafeGetCertsWithoutVerification(apkPath);
+                nonstreamingDigests = v3Signer.contentDigests;
+                nonstreamingCerts = new Certificate[][]{v3Signer.certs};
+                if (v3Signer.por != null) {
+                    // populate proof-of-rotation information
+                    pastSignerSigs = new Signature[v3Signer.por.certs.size()];
+                    for (int i = 0; i < pastSignerSigs.length; i++) {
+                        pastSignerSigs[i] = new Signature(
+                                v3Signer.por.certs.get(i).getEncoded());
+                        pastSignerSigs[i].setFlags(v3Signer.por.flagsList.get(i));
                     }
                 }
+                v3BlockId = v3Signer.blockId;
+            } catch (SignatureNotFoundException e) {
+                try {
+                    ApkSignatureSchemeV2Verifier.VerifiedSigner v2Signer =
+                            ApkSignatureSchemeV2Verifier.verify(apkPath, false);
+                    nonstreamingDigests = v2Signer.contentDigests;
+                    nonstreamingCerts = v2Signer.certs;
+                } catch (SignatureNotFoundException ee) {
+                    throw new SecurityException(
+                            "V4 verification failed to collect V2/V3 certificates from : "
+                                    + apkPath, ee);
+                }
+            }
 
+            ApkSignatureSchemeV4Verifier.VerifiedSigner vSigner =
+                    ApkSignatureSchemeV4Verifier.verify(apkPath, v4Signature, v3BlockId);
+            Certificate[][] signerCerts = new Certificate[][]{vSigner.certs};
+            Signature[] signerSigs = convertToSignatures(signerCerts);
+
+            if (verifyFull) {
                 Signature[] nonstreamingSigs = convertToSignatures(nonstreamingCerts);
                 if (nonstreamingSigs.length != signerSigs.length) {
                     throw new SecurityException(
@@ -260,7 +268,7 @@
         } catch (SignatureNotFoundException e) {
             throw e;
         } catch (Exception e) {
-            // APK Signature Scheme v4 signature found but did not verify
+            // APK Signature Scheme v4 signature found but did not verify.
             return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                     "Failed to collect certificates from " + apkPath
                             + " using APK Signature Scheme v4", e);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f74008b..2c4b80b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -60,6 +60,7 @@
 import android.view.InputDevice;
 import android.view.IInputFilter;
 import android.view.AppTransitionAnimationSpec;
+import android.view.TaskTransitionSpec;
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
 import android.view.SurfaceControl;
@@ -883,4 +884,17 @@
      * @hide
      */
     void setTaskSnapshotEnabled(boolean enabled);
+
+    /**
+     * Customized the task transition animation with a task transition spec.
+     *
+     * @param spec the spec that will be used to customize the task animations
+     */
+    void setTaskTransitionSpec(in TaskTransitionSpec spec);
+
+    /**
+     * Clears any task transition spec that has been previously set and
+     * reverts to using the default task transition with no spec changes.
+     */
+    void clearTaskTransitionSpec();
 }
diff --git a/core/java/android/view/TaskTransitionSpec.aidl b/core/java/android/view/TaskTransitionSpec.aidl
new file mode 100644
index 0000000..08af15c
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.aidl
@@ -0,0 +1,20 @@
+/*
+** 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.view;
+
+/** @hide */
+parcelable TaskTransitionSpec;
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
new file mode 100644
index 0000000..e90d6e1
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Holds information about how to execute task transition animations.
+ *
+ * This class is intended to be used with IWindowManager.setTaskTransitionSpec methods when
+ * we want more customization over the way default task transitions are executed.
+ *
+ * @hide
+ */
+public class TaskTransitionSpec implements Parcelable {
+    /**
+     * The background color to use during task animations (override the default background color)
+     */
+    public final int backgroundColor;
+
+    /**
+     * TEMPORARY FIELD (b/202383002)
+     * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
+     *
+     * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds
+     * of the task during the animation. Used to make sure that task animate above the taskbar.
+     * Will also be used to crop to the frame size of the inset source to the inset size to prevent
+     * the taskbar rounded corners overlay from being invisible during task transition animation.
+     */
+    public final Set<Integer> animationBoundInsets;
+
+    public TaskTransitionSpec(
+            int backgroundColor, Set<Integer> animationBoundInsets) {
+        this.backgroundColor = backgroundColor;
+        this.animationBoundInsets = animationBoundInsets;
+    }
+
+    public TaskTransitionSpec(Parcel in) {
+        this.backgroundColor = in.readInt();
+
+        int animationBoundInsetsSize = in.readInt();
+        this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize);
+        for (int i = 0; i < animationBoundInsetsSize; i++) {
+            this.animationBoundInsets.add(in.readInt());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(backgroundColor);
+
+        dest.writeInt(animationBoundInsets.size());
+        for (int insetType : animationBoundInsets) {
+            dest.writeInt(insetType);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
+            CREATOR = new Parcelable.Creator<TaskTransitionSpec>() {
+                public TaskTransitionSpec createFromParcel(Parcel in) {
+                    return new TaskTransitionSpec(in);
+                }
+
+                public TaskTransitionSpec[] newArray(int size) {
+                    return new TaskTransitionSpec[size];
+                }
+            };
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9030828..dabe1e9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22357,6 +22357,20 @@
     }
 
     /**
+     * If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
+     *
+     * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
+     * HW accelerated, it can't handle drawing RenderNodes.
+     *
+     * @hide
+     */
+    protected final boolean drawsWithRenderNode(Canvas canvas) {
+        return mAttachInfo != null
+                && mAttachInfo.mHardwareAccelerated
+                && canvas.isHardwareAccelerated();
+    }
+
+    /**
      * This method is called by ViewGroup.drawChild() to have each child view draw itself.
      *
      * This is where the View specializes rendering behavior based on layer type,
@@ -22365,14 +22379,8 @@
     boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
 
         final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
-        /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
-         *
-         * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
-         * HW accelerated, it can't handle drawing RenderNodes.
-         */
-        boolean drawingWithRenderNode = mAttachInfo != null
-                && mAttachInfo.mHardwareAccelerated
-                && hardwareAcceleratedCanvas;
+
+        boolean drawingWithRenderNode = drawsWithRenderNode(canvas);
 
         boolean more = false;
         final boolean childHasIdentityMatrix = hasIdentityMatrix();
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d229cf6..fe67232 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4266,7 +4266,7 @@
         int transientIndex = transientCount != 0 ? 0 : -1;
         // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
         // draw reordering internally
-        final ArrayList<View> preorderedList = isHardwareAccelerated()
+        final ArrayList<View> preorderedList = drawsWithRenderNode(canvas)
                 ? null : buildOrderedChildList();
         final boolean customOrder = preorderedList == null
                 && isChildrenDrawingOrderEnabled();
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index ff6f8ac..6bfb14b 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -740,7 +740,10 @@
                     CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
                     CONTENT_CHANGE_TYPE_PANE_TITLE,
                     CONTENT_CHANGE_TYPE_PANE_APPEARED,
-                    CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
+                    CONTENT_CHANGE_TYPE_PANE_DISAPPEARED,
+                    CONTENT_CHANGE_TYPE_DRAG_STARTED,
+                    CONTENT_CHANGE_TYPE_DRAG_DROPPED,
+                    CONTENT_CHANGE_TYPE_DRAG_CANCELLED
             })
     public @interface ContentChangeTypes {}
 
@@ -989,6 +992,9 @@
             case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED";
             case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED:
                 return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED";
+            case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
+            case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
+            case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
             default: return Integer.toHexString(type);
         }
     }
@@ -1047,6 +1053,7 @@
     /**
      * Sets the event type.
      *
+     * <b>Note: An event must represent a single event type.</b>
      * @param eventType The event type.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index a8003a1..d69a240 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -20,5 +20,4 @@
     void onSimSecureStateChanged(boolean simSecure);
     void onInputRestrictedStateChanged(boolean inputRestricted);
     void onTrustedChanged(boolean trusted);
-    void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper);
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 5144a91..4c519f4 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -117,6 +117,11 @@
      */
     public static final int ACTION_LOCKSCREEN_UNLOCK = 11;
 
+    /**
+     * Time it takes to switch users.
+     */
+    public static final int ACTION_USER_SWITCH = 12;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -129,7 +134,8 @@
         ACTION_START_RECENTS_ANIMATION,
         ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
-        ACTION_LOCKSCREEN_UNLOCK
+        ACTION_LOCKSCREEN_UNLOCK,
+        ACTION_USER_SWITCH
     };
 
     /** @hide */
@@ -145,7 +151,8 @@
         ACTION_START_RECENTS_ANIMATION,
         ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
-        ACTION_LOCKSCREEN_UNLOCK
+        ACTION_LOCKSCREEN_UNLOCK,
+        ACTION_USER_SWITCH
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -163,7 +170,8 @@
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -247,6 +255,8 @@
                 return "ACTION_ROTATE_SCREEN_SENSOR";
             case 12:
                 return "ACTION_LOCKSCREEN_UNLOCK";
+            case 13:
+                return "ACTION_USER_SWITCH";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
@@ -424,7 +434,7 @@
             // start counting timeout.
             mTimeoutRunnable = timeoutAction;
             BackgroundThread.getHandler()
-                    .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(2));
+                    .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15));
         }
 
         void end() {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d0910fc..9948b36 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4926,15 +4926,15 @@
                 android:protectionLevel="signature|privileged" />
 
     <!-- An application needs this permission for
-         {@link android.provider.Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK} to show its
-         {@link android.app.Activity} in 2-pane of Settings app. -->
-    <permission android:name="android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK"
+         {@link android.provider.Settings#ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY} to show its
+         {@link android.app.Activity} embedded in Settings app. -->
+    <permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"
                 android:protectionLevel="signature|preinstalled" />
 
     <!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only
-         the settings app can embed it in a 2-pane window.
+         the settings app can embed it in a multi pane window.
          @hide -->
-    <permission android:name="android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS"
+    <permission android:name="android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"
                 android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows applications to set a live wallpaper.
@@ -6311,7 +6311,7 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-        <service android:name="com.android.server.pm.BackgroundDexOptService"
+        <service android:name="com.android.server.pm.BackgroundDexOptJobService"
                  android:exported="true"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1b67328..14a1ae5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1457,7 +1457,7 @@
     <string name="usb_power_notification_message" msgid="7284765627437897702">"جارٍ شحن الجهاز المتصل. انقر لعرض خيارات أكثر."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"تم اكتشاف ملحق صوتي تناظري"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"الجهاز الذي تم توصيله بالهاتف غير متوافق معه. انقر للحصول على المزيد من المعلومات."</string>
-    <string name="adb_active_notification_title" msgid="408390247354560331">"‏تم توصيل أداة تصحيح أخطاء الجهاز عبر USB"</string>
+    <string name="adb_active_notification_title" msgid="408390247354560331">"‏تم توصيل USB لتصحيح أخطاء الجهاز"</string>
     <string name="adb_active_notification_message" msgid="5617264033476778211">"‏انقر لإيقاف تصحيح أخطاء الجهاز عبر USB."</string>
     <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"‏اختيار إيقاف تصحيح أخطاء USB."</string>
     <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"تم تفعيل ميزة \"تصحيح الأخطاء اللاسلكي\"."</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 2e8bf70..6aa9330 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -299,7 +299,7 @@
     <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्‍या तपशीलांसाठी टॅप करा"</string>
     <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="8974401416068943888">"सुरक्षित मोड"</string>
-    <string name="android_system_label" msgid="5974767339591067210">"Android सिस्‍टम"</string>
+    <string name="android_system_label" msgid="5974767339591067210">"Android सिस्‍टीम"</string>
     <string name="user_owner_label" msgid="8628726904184471211">"वैयक्तिक प्रोफाइलवर स्विच करा"</string>
     <string name="managed_profile_label" msgid="7316778766973512382">"कार्य प्रोफाइलवर स्विच करा"</string>
     <string name="permgrouplab_contacts" msgid="4254143639307316920">"संपर्क"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 4cfcd80..7f7c6b6 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -360,7 +360,7 @@
     <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ਐਪ ਨੂੰ ਆਉਣ ਵਾਲੀ ਫ਼ੋਨ ਕਾਲ ਦਾ ਜਵਾਬ ਦੇਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦੀ ਹੈ।"</string>
     <string name="permlab_receiveSms" msgid="505961632050451881">"ਲਿਖਤ ਸੁਨੇਹੇ (SMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="permdesc_receiveSms" msgid="1797345626687832285">"ਐਪ ਨੂੰ SMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
-    <string name="permlab_receiveMms" msgid="4000650116674380275">"ਟੈਕਸਟ ਸੁਨੇਹੇ (MMS) ਪੜ੍ਹੋ"</string>
+    <string name="permlab_receiveMms" msgid="4000650116674380275">"ਲਿਖਤ ਸੁਨੇਹੇ (MMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="permdesc_receiveMms" msgid="958102423732219710">"ਐਪ ਨੂੰ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string>
     <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string>
     <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਹਨਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਚੇਤਨਾਵਾਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string>
@@ -376,7 +376,7 @@
     <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
     <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"ਇਹ ਐਪ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
     <string name="permdesc_readSms" product="default" msgid="774753371111699782">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
-    <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਟੈਕਸਟ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+    <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਲਿਖਤ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"ਐਪ ਨੂੰ WAP ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸ ਇਜਾਜ਼ਤ ਵਿੱਚ ਸ਼ਾਮਲ ਹੈ ਐਪ ਦੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰਨ ਅਤੇ ਮਿਟਾਉਣ ਦੀ ਸਮਰੱਥਾ।"</string>
     <string name="permlab_getTasks" msgid="7460048811831750262">"ਚੱਲ ਰਹੇ ਐਪਸ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
     <string name="permdesc_getTasks" msgid="7388138607018233726">"ਐਪ ਨੂੰ ਵਰਤਮਾਨ ਵਿੱਚ ਅਤੇ ਹੁਣੇ ਜਿਹੇ ਚੱਲ ਰਹੇ ਕੰਮਾਂ ਬਾਰੇ ਵਿਸਤ੍ਰਿਤ ਜਾਣਕਾਰੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਐਪ ਨੂੰ ਇਸ ਬਾਰੇ ਜਾਣਕਾਰੀ ਖੋਜਣ ਦੀ ਆਗਿਆ ਦੇ ਸਕਦਾ ਹੈ ਕਿ ਡੀਵਾਈਸ ਤੇ ਕਿਹੜੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।"</string>
@@ -1050,7 +1050,7 @@
     <string name="searchview_description_query" msgid="7430242366971716338">"ਖੋਜ ਪੁੱਛਗਿੱਛ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"ਸਵਾਲ ਹਟਾਓ"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"ਸਵਾਲ ਪ੍ਰਸਤੁਤ ਕਰੋ"</string>
-    <string name="searchview_description_voice" msgid="42360159504884679">"ਵੌਇਸ ਖੋਜ"</string>
+    <string name="searchview_description_voice" msgid="42360159504884679">"ਅਵਾਜ਼ੀ ਖੋਜ"</string>
     <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"ਕੀ ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
     <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਣਾ ਚਾਹੁੰਦੀ ਹੈ। ਜਦੋਂ \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਕਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਟੈਬਲੈੱਟ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤਾਂ ਦੀ ਪਾਲਣਾ ਕਰ ਸਕਦੇ ਹੋ।"</string>
     <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਚਾਹੁੰਦਾ ਹੈ। ਜਦੋਂ ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਤਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਫ਼ੋਨ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤ ਪਰਫੌਰਮ ਕਰ ਸਕਦੇ ਹੋ।"</string>
@@ -2015,7 +2015,7 @@
     <string name="app_category_image" msgid="7307840291864213007">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਚਿੱਤਰ"</string>
     <string name="app_category_social" msgid="2278269325488344054">"ਸਮਾਜਕ ਅਤੇ ਸੰਚਾਰ"</string>
     <string name="app_category_news" msgid="1172762719574964544">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string>
-    <string name="app_category_maps" msgid="6395725487922533156">"Maps ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string>
+    <string name="app_category_maps" msgid="6395725487922533156">"ਨਕਸ਼ੇ ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string>
     <string name="app_category_productivity" msgid="1844422703029557883">"ਉਤਪਾਦਕਤਾ"</string>
     <string name="app_category_accessibility" msgid="6643521607848547683">"ਪਹੁੰਚਯੋਗਤਾ"</string>
     <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"ਡੀਵਾਈਸ ਸਟੋਰੇਜ"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 2b5ea2e..8f8325a 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -176,10 +176,10 @@
     <string name="contentServiceSync" msgid="2341041749565687871">"ஒத்திசை"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ஒத்திசைக்க முடியவில்லை"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"அதிகளவிலான <xliff:g id="CONTENT_TYPE">%s</xliff:g> உள்ளடக்க வகைகளை நீக்க முயன்றுள்ளீர்கள்."</string>
-    <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string>
-    <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை நீக்கவும்."</string>
+    <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string>
+    <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string>
     <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TVயின் சேமிப்பிடம் நிரம்பிவிட்டது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string>
-    <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string>
+    <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string>
     <plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029">
       <item quantity="other">சான்றிதழ் அங்கீகாரங்கள் நிறுவப்பட்டன</item>
       <item quantity="one">சான்றிதழ் அங்கீகாரம் நிறுவப்பட்டது</item>
@@ -311,7 +311,7 @@
     <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS அனுப்பலாம், வந்த SMSகளைப் பார்க்கலாம்"</string>
     <string name="permgrouplab_storage" msgid="1938416135375282333">"ஃபைல்களும் மீடியாவும்"</string>
-    <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் கோப்புகளை அணுக வேண்டும்"</string>
+    <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் ஃபைல்களை அணுக வேண்டும்"</string>
     <string name="permgrouplab_microphone" msgid="2480597427667420076">"மைக்ரோஃபோன்"</string>
     <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ஒலிப் பதிவு செய்யலாம்"</string>
     <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"உடல் செயல்பாடுகள்"</string>
@@ -1418,7 +1418,7 @@
     <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"அமைக்கத் தேர்ந்தெடுங்கள்"</string>
     <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"சாதனத்தை ரீஃபார்மேட் செய்ய வேண்டியிருக்கும். வெளியேற்ற தட்டவும்."</string>
     <string name="ext_media_ready_notification_message" msgid="777258143284919261">"படங்களையும் மீடியாவையும் மாற்றலாம்"</string>
-    <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா கோப்புகளை உலாவுக"</string>
+    <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா ஃபைல்களை உலாவுக"</string>
     <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"<xliff:g id="NAME">%s</xliff:g> இல் சிக்கல்"</string>
     <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> வேலை செய்யவில்லை"</string>
     <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"சரிசெய்ய, தட்டவும்"</string>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index 224db02..62959b7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -28,7 +28,7 @@
      * an OEM by overriding this method.
      */
     public static SidecarInterface getSidecarImpl(Context context) {
-        return new SampleSidecarImpl(context);
+        return new SampleSidecarImpl(context.getApplicationContext());
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 10d7725..6a252e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -232,7 +232,7 @@
 
             if (mSplitLayout != null
                     && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
-                onLayoutChanged(mSplitLayout);
+                onLayoutSizeChanged(mSplitLayout);
             }
         } else if (taskInfo.taskId == getTaskId1()) {
             mTaskInfo1 = taskInfo;
@@ -313,13 +313,19 @@
     }
 
     @Override
-    public void onLayoutChanging(SplitLayout layout) {
+    public void onLayoutPositionChanging(SplitLayout layout) {
         mSyncQueue.runInSync(t ->
                 layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
     }
 
     @Override
-    public void onLayoutChanged(SplitLayout layout) {
+    public void onLayoutSizeChanging(SplitLayout layout) {
+        mSyncQueue.runInSync(t ->
+                layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
+    }
+
+    @Override
+    public void onLayoutSizeChanged(SplitLayout layout) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
         mSyncQueue.queue(wct);
@@ -328,9 +334,9 @@
     }
 
     @Override
-    public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+    public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        layout.applyLayoutShifted(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
+        layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
         mController.getTaskOrganizer().applyTransaction(wct);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 05ebbba..8d43f13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -121,7 +121,7 @@
     @Nullable
     private Icon mIcon;
     private boolean mIsBubble;
-    private boolean mIsVisuallyInterruptive;
+    private boolean mIsTextChanged;
     private boolean mIsClearable;
     private boolean mShouldSuppressNotificationDot;
     private boolean mShouldSuppressNotificationList;
@@ -342,12 +342,12 @@
     }
 
     /**
-     * Sets whether this bubble is considered visually interruptive. This method is purely for
+     * Sets whether this bubble is considered text changed. This method is purely for
      * testing.
      */
     @VisibleForTesting
-    void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
-        mIsVisuallyInterruptive = visuallyInterruptive;
+    void setTextChangedForTest(boolean textChanged) {
+        mIsTextChanged = textChanged;
     }
 
     /**
@@ -454,7 +454,7 @@
         mFlyoutMessage = extractFlyoutMessage(entry);
         if (entry.getRanking() != null) {
             mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
-            mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
+            mIsTextChanged = entry.getRanking().isTextChanged();
             if (entry.getRanking().getChannel() != null) {
                 mIsImportantConversation =
                         entry.getRanking().getChannel().isImportantConversation();
@@ -495,8 +495,8 @@
         return mIcon;
     }
 
-    boolean isVisuallyInterruptive() {
-        return mIsVisuallyInterruptive;
+    boolean isTextChanged() {
+        return mIsTextChanged;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index c126f32..b6d65be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -939,7 +939,7 @@
     public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
         // If this is an interruptive notif, mark that it's interrupted
         mSysuiProxy.setNotificationInterruption(notif.getKey());
-        if (!notif.getRanking().visuallyInterruptive()
+        if (!notif.getRanking().isTextChanged()
                 && (notif.getBubbleMetadata() != null
                     && !notif.getBubbleMetadata().getAutoExpandBubble())
                 && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index bef26bf..519a856 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -323,7 +323,7 @@
         }
         mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
         Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
-        suppressFlyout |= !bubble.isVisuallyInterruptive();
+        suppressFlyout |= !bubble.isTextChanged();
 
         if (prevBubble == null) {
             // Create a new bubble
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d590ab1..300319a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -120,8 +120,6 @@
 
     private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
 
-    private static final int MANAGE_MENU_SCRIM_ANIM_DURATION = 150;
-
     private static final float SCRIM_ALPHA = 0.6f;
 
     /**
@@ -894,6 +892,7 @@
                         updatePointerPosition(false /* forIme */);
                         mExpandedAnimationController.expandFromStack(() -> {
                             afterExpandedViewAnimation();
+                            showManageMenu(mShowingManage);
                         } /* after */);
                         final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
                                 getBubbleIndex(mExpandedBubble));
@@ -1253,9 +1252,6 @@
         mRelativeStackPositionBeforeRotation = new RelativeStackPosition(
                 mPositioner.getRestingPosition(),
                 mStackAnimationController.getAllowableStackPositionRegion());
-        mManageMenu.setVisibility(View.INVISIBLE);
-        mShowingManage = false;
-
         addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
     }
@@ -2555,16 +2551,19 @@
         invalidate();
     }
 
-    private void showManageMenu(boolean show) {
+    /** Hide or show the manage menu for the currently expanded bubble. */
+    @VisibleForTesting
+    public void showManageMenu(boolean show) {
         mShowingManage = show;
 
         // This should not happen, since the manage menu is only visible when there's an expanded
         // bubble. If we end up in this state, just hide the menu immediately.
         if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
             mManageMenu.setVisibility(View.INVISIBLE);
+            mManageMenuScrim.setVisibility(INVISIBLE);
+            mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
             return;
         }
-
         if (show) {
             mManageMenuScrim.setVisibility(VISIBLE);
             mManageMenuScrim.setTranslationZ(mManageMenu.getElevation() - 1f);
@@ -2576,8 +2575,8 @@
             }
         };
 
+        mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
         mManageMenuScrim.animate()
-                .setDuration(MANAGE_MENU_SCRIM_ANIM_DURATION)
                 .setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
                 .alpha(show ? SCRIM_ALPHA : 0f)
                 .withEndAction(endAction)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 9b7eb2f..c82249b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -284,6 +284,8 @@
 
         void onStackExpandChanged(boolean shouldExpand);
 
+        void onManageMenuExpandChanged(boolean menuExpanded);
+
         void onUnbubbleConversation(String key);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 596a2f4..5b3ce2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -291,13 +291,13 @@
     void updateDivideBounds(int position) {
         updateBounds(position);
         mSplitWindowManager.setResizingSplits(true);
-        mSplitLayoutHandler.onLayoutChanging(this);
+        mSplitLayoutHandler.onLayoutSizeChanging(this);
     }
 
     void setDividePosition(int position) {
         mDividePosition = position;
         updateBounds(mDividePosition);
-        mSplitLayoutHandler.onLayoutChanged(this);
+        mSplitLayoutHandler.onLayoutSizeChanged(this);
         mSplitWindowManager.setResizingSplits(false);
     }
 
@@ -451,7 +451,7 @@
      * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
      * restore shifted configuration bounds if it's no longer shifted.
      */
-    public void applyLayoutShifted(WindowContainerTransaction wct, int offsetX, int offsetY,
+    public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
             ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
         if (offsetX == 0 && offsetY == 0) {
             wct.setBounds(taskInfo1.token, mBounds1);
@@ -492,19 +492,43 @@
         /** Calls when dismissing split. */
         void onSnappedToDismiss(boolean snappedToEnd);
 
-        /** Calls when the bounds is changing due to animation or dragging divider bar. */
-        void onLayoutChanging(SplitLayout layout);
-
-        /** Calls when the target bounds changed. */
-        void onLayoutChanged(SplitLayout layout);
+        /**
+         * Calls when resizing the split bounds.
+         *
+         * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+         * SurfaceControl, SurfaceControl)
+         */
+        void onLayoutSizeChanging(SplitLayout layout);
 
         /**
-         * Notifies when the layout shifted. So the layout handler can shift configuration
-         * bounds correspondingly to make sure client apps won't get configuration changed or
-         * relaunch. If the layout is no longer shifted, layout handler should restore shifted
-         * configuration bounds.
+         * Calls when finish resizing the split bounds.
+         *
+         * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo,
+         * ActivityManager.RunningTaskInfo)
+         * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+         * SurfaceControl, SurfaceControl)
          */
-        void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout);
+        void onLayoutSizeChanged(SplitLayout layout);
+
+        /**
+         * Calls when re-positioning the split bounds. Like moving split bounds while showing IME
+         * panel.
+         *
+         * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+         * SurfaceControl, SurfaceControl)
+         */
+        void onLayoutPositionChanging(SplitLayout layout);
+
+        /**
+         * Notifies the target offset for shifting layout. So layout handler can shift configuration
+         * bounds correspondingly to make sure client apps won't get configuration changed or
+         * relaunched. If the layout is no longer shifted, layout handler should restore shifted
+         * configuration bounds.
+         *
+         * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int,
+         * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo)
+         */
+        void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout);
 
         /** Calls when user double tapped on the divider bar. */
         default void onDoubleTappedDivider() {
@@ -674,9 +698,9 @@
                 // changed or relaunch. This is required to make sure client apps will calculate
                 // insets properly after layout shifted.
                 if (mTargetYOffset == 0) {
-                    mSplitLayoutHandler.onLayoutShifted(0, 0, SplitLayout.this);
+                    mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
                 } else {
-                    mSplitLayoutHandler.onLayoutShifted(0, mTargetYOffset - mLastYOffset,
+                    mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
                             SplitLayout.this);
                 }
             }
@@ -695,7 +719,7 @@
         public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
             if (displayId != mDisplayId) return;
             onProgress(getProgress(imeTop));
-            mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+            mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
         }
 
         @Override
@@ -703,7 +727,7 @@
                 SurfaceControl.Transaction t) {
             if (displayId != mDisplayId || cancel) return;
             onProgress(1.0f);
-            mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+            mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
         }
 
         @Override
@@ -713,7 +737,7 @@
             if (!controlling && mImeShown) {
                 reset();
                 mSplitWindowManager.setInteractive(true);
-                mSplitLayoutHandler.onLayoutChanging(SplitLayout.this);
+                mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 0d52719..ec71fbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
@@ -213,7 +215,11 @@
         options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
 
         try {
-            ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+            final int result =
+                    ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+            if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
+                mStageCoordinator.evictOccludedChildren(position);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to launch task", e);
         }
@@ -229,6 +235,7 @@
                     mContext.getSystemService(LauncherApps.class);
             launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
                     options, user);
+            mStageCoordinator.evictOccludedChildren(position);
         } catch (ActivityNotFoundException e) {
             Slog.e(TAG, "Failed to launch shortcut", e);
         }
@@ -272,6 +279,10 @@
                         Slog.e(TAG, "Error finishing legacy transition: ", e);
                     }
                 }
+
+                // Launching a new app into a specific split evicts tasks previously in the same
+                // split.
+                mStageCoordinator.evictOccludedChildren(position);
             }
         };
         WindowContainerTransaction wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 3e74ad3..0cff18e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -394,6 +394,12 @@
                 TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
     }
 
+    void evictOccludedChildren(@SplitPosition int position) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        (position == mSideStagePosition ? mSideStage : mMainStage).evictOccludedChildren(wct);
+        mTaskOrganizer.applyTransaction(wct);
+    }
+
     Bundle resolveStartStage(@SplitScreen.StageType int stage,
             @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
             @androidx.annotation.Nullable WindowContainerTransaction wct) {
@@ -471,7 +477,7 @@
         if (mSideStageListener.mVisible && updateBounds) {
             if (wct == null) {
                 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
-                onLayoutChanged(mSplitLayout);
+                onLayoutSizeChanged(mSplitLayout);
             } else {
                 updateWindowBounds(mSplitLayout, wct);
                 updateUnfoldBounds();
@@ -799,13 +805,18 @@
     }
 
     @Override
-    public void onLayoutChanging(SplitLayout layout) {
+    public void onLayoutPositionChanging(SplitLayout layout) {
+        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+    }
+
+    @Override
+    public void onLayoutSizeChanging(SplitLayout layout) {
         mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
         mSideStage.setOutlineVisibility(false);
     }
 
     @Override
-    public void onLayoutChanged(SplitLayout layout) {
+    public void onLayoutSizeChanged(SplitLayout layout) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         updateUnfoldBounds();
@@ -859,13 +870,13 @@
     }
 
     @Override
-    public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+    public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+        layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
                 bottomRightStage.mRootTaskInfo);
         mTaskOrganizer.applyTransaction(wct);
     }
@@ -897,7 +908,7 @@
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
                 && mMainStage.isActive()) {
-            onLayoutChanged(mSplitLayout);
+            onLayoutSizeChanged(mSplitLayout);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 84d570f..071badf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -68,6 +68,7 @@
         void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
 
         void onRootTaskVanished();
+
         void onNoLongerSupportMultiWindow();
     }
 
@@ -247,6 +248,15 @@
         wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
     }
 
+    void evictOccludedChildren(WindowContainerTransaction wct) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+            if (!taskInfo.isVisible) {
+                wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+            }
+        }
+    }
+
     void setVisibility(boolean visible, WindowContainerTransaction wct) {
         wct.reorder(mRootTaskInfo.token, visible /* onTop */);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
index 2f75f8b..574e379 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -470,8 +470,8 @@
 
         if (mSideStageListener.mVisible && updateBounds) {
             if (wct == null) {
-                // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
-                onLayoutChanged(mSplitLayout);
+                // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
+                onLayoutSizeChanged(mSplitLayout);
             } else {
                 updateWindowBounds(mSplitLayout, wct);
                 updateUnfoldBounds();
@@ -800,13 +800,18 @@
     }
 
     @Override
-    public void onLayoutChanging(SplitLayout layout) {
+    public void onLayoutPositionChanging(SplitLayout layout) {
+        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+    }
+
+    @Override
+    public void onLayoutSizeChanging(SplitLayout layout) {
         mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
         mSideStage.setOutlineVisibility(false);
     }
 
     @Override
-    public void onLayoutChanged(SplitLayout layout) {
+    public void onLayoutSizeChanged(SplitLayout layout) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         updateUnfoldBounds();
@@ -860,13 +865,13 @@
     }
 
     @Override
-    public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+    public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+        layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
                 bottomRightStage.mRootTaskInfo);
         mTaskOrganizer.applyTransaction(wct);
     }
@@ -898,7 +903,7 @@
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
                 && mMainStage.isActive()) {
-            onLayoutChanged(mSplitLayout);
+            onLayoutSizeChanged(mSplitLayout);
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 091022a..bc701d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -131,7 +131,7 @@
 
         NotificationListenerService.Ranking ranking =
                 mock(NotificationListenerService.Ranking.class);
-        when(ranking.visuallyInterruptive()).thenReturn(true);
+        when(ranking.isTextChanged()).thenReturn(true);
         mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
         mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
                 mMainExecutor);
@@ -1014,15 +1014,15 @@
     }
 
     private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
-        sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+        sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
     }
 
     private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
-            boolean visuallyInterruptive) {
+            boolean textChanged) {
         setPostTime(entry, postTime);
         // BubbleController calls this:
         Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
-        b.setVisuallyInterruptiveForTest(visuallyInterruptive);
+        b.setTextChangedForTest(textChanged);
         // And then this
         mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
                 true /* showInShade */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index defa58d..b4caeb5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -95,13 +95,13 @@
     @Test
     public void testUpdateDivideBounds() {
         mSplitLayout.updateDivideBounds(anyInt());
-        verify(mSplitLayoutHandler).onLayoutChanging(any(SplitLayout.class));
+        verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
     }
 
     @Test
     public void testSetDividePosition() {
         mSplitLayout.setDividePosition(anyInt());
-        verify(mSplitLayoutHandler).onLayoutChanged(any(SplitLayout.class));
+        verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index aeb2849b..cd29220 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -133,7 +133,7 @@
         mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
         clearInvocations(mMainUnfoldController, mSideUnfoldController);
 
-        mStageCoordinator.onLayoutChanged(mSplitLayout);
+        mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
 
         verify(mMainUnfoldController).onLayoutChanged(mBounds2);
         verify(mSideUnfoldController).onLayoutChanged(mBounds1);
@@ -145,7 +145,7 @@
         mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
         clearInvocations(mMainUnfoldController, mSideUnfoldController);
 
-        mStageCoordinator.onLayoutChanged(mSplitLayout);
+        mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
 
         verify(mMainUnfoldController).onLayoutChanged(mBounds1);
         verify(mSideUnfoldController).onLayoutChanged(mBounds2);
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5fa0922..99ecaa8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -18,15 +18,16 @@
 
 #include <apex/window.h>
 #include <fcntl.h>
+#include <gui/TraceUtils.h>
 #include <strings.h>
 #include <sys/stat.h>
+#include <ui/Fence.h>
 
 #include <algorithm>
 #include <cstdint>
 #include <cstdlib>
 #include <functional>
 
-#include <gui/TraceUtils.h>
 #include "../Properties.h"
 #include "AnimationContext.h"
 #include "Frame.h"
@@ -739,6 +740,9 @@
             instance->mRenderThread.getASurfaceControlFunctions();
 
     nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
+    if (gpuCompleteTime == Fence::SIGNAL_TIME_PENDING) {
+        gpuCompleteTime = -1;
+    }
     uint64_t frameNumber = functions.getFrameNumberFunc(stats);
 
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
diff --git a/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
index 94668a3..6d69038 100644
--- a/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger/RecognitionEvent.aidl
@@ -48,4 +48,13 @@
     @nullable AudioConfig audioConfig;
     /** Additional data. */
     byte[] data;
+    /**
+     * If true, recognition is still active after this event.
+     * For compatibility with earlier versions of this data type, when the status field is set to
+     * RecognitionStatus.FORCED, the value of this field should be treated as 'true', regardless of
+     * the actual value.
+     * When the status is RecognitionStatus.ABORTED or RecognitionStatus.FAILURE, this must be set
+     * to false.
+     */
+     boolean recognitionStillActive;
 }
diff --git a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
index e6cfb6b..0209602 100644
--- a/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
+++ b/media/aidl_api/android.media.soundtrigger.types/current/android/media/soundtrigger/RecognitionEvent.aidl
@@ -43,4 +43,5 @@
   boolean triggerInData;
   @nullable android.media.audio.common.AudioConfig audioConfig;
   byte[] data;
+  boolean recognitionStillActive;
 }
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 7e9d2d8..9c9e83b 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -106,7 +106,7 @@
     @IntDef({
             TYPE_UNKNOWN, TYPE_BUILTIN_SPEAKER, TYPE_WIRED_HEADSET,
             TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, TYPE_USB_DEVICE,
-            TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID,
+            TYPE_USB_ACCESSORY, TYPE_DOCK, TYPE_USB_HEADSET, TYPE_HEARING_AID, TYPE_BLE_HEADSET,
             TYPE_REMOTE_TV, TYPE_REMOTE_SPEAKER, TYPE_GROUP})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
@@ -202,6 +202,14 @@
     public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
 
     /**
+     * A route type describing a BLE HEADSET.
+     *
+     * @see #getType
+     * @hide
+     */
+    public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
+
+    /**
      * A route type indicating the presentation of the media is happening on a TV.
      *
      * @see #getType
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index fa3dc72..6112290 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -504,8 +504,22 @@
         /**
          * Sets target mix role of the mixing rule.
          *
-         * <p>The mix role indicates playback streams will be captured or recording source will be
-         * injected. If not specified, the mix role will be decided automatically when
+         * As each mixing rule is intended to be associated with an {@link AudioMix},
+         * explicitly setting the role of a mixing rule allows this {@link Builder} to
+         * verify validity of the mixing rules to be validated.<br>
+         * The mix role allows distinguishing between:
+         * <ul>
+         * <li>audio framework mixers that will mix / sample-rate convert / reformat the audio
+         *     signal of any audio player (e.g. a {@link android.media.MediaPlayer}) that matches
+         *     the selection rules defined in the object being built. Use
+         *     {@link AudioMixingRule#MIX_ROLE_PLAYERS} for such an {@code AudioMixingRule}</li>
+         * <li>audio framework mixers that will be used as the injection point (after sample-rate
+         *     conversion and reformatting of the audio signal) into any audio recorder (e.g. a
+         *     {@link android.media.AudioRecord}) that matches the selection rule defined in the
+         *     object being built. Use {@link AudioMixingRule#MIX_ROLE_INJECTOR} for such an
+         *     {@code AudioMixingRule}.</li>
+         * </ul>
+         * <p>If not specified, the mix role will be decided automatically when
          * {@link #addRule(AudioAttributes, int)} or {@link #addMixRule(int, Object)} be called.
          *
          * @param mixRole integer value of {@link #MIX_ROLE_PLAYERS} or {@link #MIX_ROLE_INJECTOR}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b4ae1fb..9dc4949 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -449,6 +449,43 @@
         mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
     }
 
+    /**
+     * Checks if there is an unused frontend resource available.
+     *
+     * @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
+     * query to be done for.
+     */
+    public boolean hasUnusedFrontend(int frontendType) {
+        return mTunerResourceManager.hasUnusedFrontend(frontendType);
+    }
+
+    /**
+     * Checks if the calling Tuner object has the lowest priority as a client to
+     * {@link TunerResourceManager}
+     *
+     * <p>The priority comparison is done against the current holders of the frontend resource.
+     *
+     * <p>The behavior of this function is independent of the availability of unused resources.
+     *
+     * <p>The function returns {@code true} in any of the following sceanrios:
+     * <ul>
+     * <li>The caller has the priority <= other clients</li>
+     * <li>No one is holding the frontend resource of the specified type</li>
+     * <li>The caller is the only one who is holding the resource</li>
+     * <li>The frontend resource of the specified type does not exist</li>
+     *
+     * </ul>
+     * @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
+     * query to be done for.
+     *
+     * @return {@code false} only if someone else with strictly lower priority is holding the
+     *         resourece.
+     *         {@code true} otherwise.
+     */
+    public boolean isLowestPriority(int frontendType) {
+        return mTunerResourceManager.isLowestPriority(mClientId, frontendType);
+    }
+
     private long mNativeContext; // used by native jMediaTuner
 
     /**
@@ -1615,4 +1652,9 @@
         }
         mLnb = null;
     }
+
+    /** @hide */
+    public int getClientId() {
+        return mClientId;
+    }
 }
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 6f7adbc..244fd0e 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -179,6 +179,96 @@
     }
 
     /**
+     * Checks if there is an unused frontend resource available.
+     *
+     * @param frontendType The frontend type for the query to be done for.
+     */
+    public boolean hasUnusedFrontend(int frontendType) {
+        boolean result = false;
+        try {
+            result = mService.hasUnusedFrontend(frontendType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Checks if the client has the lowest priority among the clients that are holding
+     * the frontend resource of the specified type.
+     *
+     * <p> When this function returns false, it means that there is at least one client with the
+     * strictly lower priority (than clientId) that is reclaimable by the system.
+     *
+     * @param clientId The client ID to be checked the priority for.
+     * @param frontendType The specific frontend type to be checked for.
+     *
+     * @return false if there is another client holding the frontend resource of the specified type
+     * that can be reclaimed. Otherwise true.
+     */
+    public boolean isLowestPriority(int clientId, int frontendType) {
+        boolean result = false;
+        try {
+            result = mService.isLowestPriority(clientId, frontendType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return result;
+    }
+
+    /**
+     * Stores the frontend resource map if it was stored before.
+     *
+     * <p>This API is only for testing purpose and should be used in pair with
+     * restoreResourceMap(), which allows testing of {@link Tuner} APIs
+     * that behave differently based on different sets of resource map.
+     *
+     * @param resourceType The resource type to store the map for.
+     */
+    public void storeResourceMap(int resourceType) {
+        try {
+            mService.storeResourceMap(resourceType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears the frontend resource map.
+     *
+     * <p>This API is only for testing purpose and should be called right after
+     * storeResourceMap(), so TRMService#removeFrontendResource() does not
+     * get called in TRMService#setFrontendInfoListInternal() for custom frontend
+     * resource map creation.
+     *
+     * @param resourceType The resource type to clear the map for.
+     */
+    public void clearResourceMap(int resourceType) {
+        try {
+            mService.clearResourceMap(resourceType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Restores Frontend resource map for the later restore.
+     *
+     * <p>This API is only for testing purpose and should be used in pair with
+     * storeResourceMap(), which allows testing of {@link Tuner} APIs
+     * that behave differently based on different sets of resource map.
+     *
+     * @param resourceType The resource type to restore the map for.
+     */
+    public void restoreResourceMap(int resourceType) {
+        try {
+            mService.restoreResourceMap(resourceType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Updates the current TRM of the TunerHAL Frontend information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index a1f6687..7bc5058 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -83,6 +83,30 @@
     boolean updateClientPriority(in int clientId, in int priority, in int niceValue);
 
     /*
+     * Checks if there is any unused frontend resource of the specified type.
+     *
+     * @param frontendType the specific type of frontend resource to be checked for.
+     *
+     * @return true if there is any unused resource of the specified type.
+     */
+    boolean hasUnusedFrontend(in int frontendType);
+
+    /*
+     * Checks if the client has the lowest priority among the clients that are holding
+     * the frontend resource of the specified type.
+     *
+     * <p> When this function returns false, it means that there is at least one client with the
+     * strictly lower priority (than clientId) that is reclaimable by the system.
+     *
+     * @param clientId The client ID to be checked the priority for.
+     * @param frontendType The specific frontend type to be checked for.
+     *
+     * @return false if there is another client holding the frontend resource of the specified type
+     * that can be reclaimed. Otherwise true.
+     */
+    boolean isLowestPriority(in int clientId, in int frontendType);
+
+    /*
      * Updates the available Frontend resources information on the current device.
      *
      * <p><strong>Note:</strong> This update must happen before the first
@@ -354,4 +378,38 @@
      */
     boolean isHigherPriority(in ResourceClientProfile challengerProfile,
             in ResourceClientProfile holderProfile);
+
+    /*
+     * Stores Frontend resource map for the later restore.
+     *
+     * <p>This is API is only for testing purpose and should be used in pair with
+     * restoreResourceMap(), which allows testing of {@link Tuner} APIs
+     * that behave differently based on different sets of resource map.
+     *
+     * @param resourceType The resource type to store the map for.
+     */
+    void storeResourceMap(in int resourceType);
+
+    /*
+     * Clears the frontend resource map.
+     *
+     * <p>This is API is only for testing purpose and should be called right after
+     * storeResourceMap(), so TRMService#removeFrontendResource() does not
+     * get called in TRMService#setFrontendInfoListInternal() for custom frontend
+     * resource map creation.
+     *
+     * @param resourceType The resource type to clear the map for.
+     */
+    void clearResourceMap(in int resourceType);
+
+    /*
+     * Restores Frontend resource map if it was stored before.
+     *
+     * <p>This is API is only for testing purpose and should be used in pair with
+     * storeResourceMap(), which allows testing of {@link Tuner} APIs
+     * that behave differently based on different sets of resource map.
+     *
+     * @param resourceType The resource type to restore the map for.
+     */
+    void restoreResourceMap(in int resourceType);
 }
diff --git a/packages/PrintSpooler/res/values-pa/strings.xml b/packages/PrintSpooler/res/values-pa/strings.xml
index 601fa83..ddcec40 100644
--- a/packages/PrintSpooler/res/values-pa/strings.xml
+++ b/packages/PrintSpooler/res/values-pa/strings.xml
@@ -50,8 +50,8 @@
     <string name="search" msgid="5421724265322228497">"ਖੋਜੋ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ਸਾਰੇ ਪ੍ਰਿੰਟਰ"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ਸੇਵਾ ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ"</string>
-    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ"</string>
+    <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ ਗਿਆ"</string>
+    <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ ਗਿਆ"</string>
     <string name="print_add_printer" msgid="1088656468360653455">"ਪ੍ਰਿੰਟਰ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="print_select_printer" msgid="7388760939873368698">"ਪ੍ਰਿੰਟਰ ਚੁਣੋ"</string>
     <string name="print_forget_printer" msgid="5035287497291910766">"ਪ੍ਰਿੰਟਰ ਭੁੱਲੋ"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index dd8f604..aede665 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -21,6 +21,7 @@
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
 import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -482,6 +483,7 @@
                 break;
             case TYPE_HEARING_AID:
             case TYPE_BLUETOOTH_A2DP:
+            case TYPE_BLE_HEADSET:
                 final BluetoothDevice device =
                         BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress());
                 final CachedBluetoothDevice cachedDevice =
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 22001c9..215e2a0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -38,6 +38,7 @@
 import com.android.settingslib.bluetooth.HearingAidProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.LeAudioProfile;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -440,6 +441,7 @@
     private boolean isActiveDevice(CachedBluetoothDevice device) {
         boolean isActiveDeviceA2dp = false;
         boolean isActiveDeviceHearingAid = false;
+        boolean isActiveLeAudio = false;
         final A2dpProfile a2dpProfile = mLocalBluetoothManager.getProfileManager().getA2dpProfile();
         if (a2dpProfile != null) {
             isActiveDeviceA2dp = device.getDevice().equals(a2dpProfile.getActiveDevice());
@@ -453,7 +455,15 @@
             }
         }
 
-        return isActiveDeviceA2dp || isActiveDeviceHearingAid;
+        if (!isActiveDeviceA2dp && !isActiveDeviceHearingAid) {
+            final LeAudioProfile leAudioProfile = mLocalBluetoothManager.getProfileManager()
+                    .getLeAudioProfile();
+            if (leAudioProfile != null) {
+                isActiveLeAudio = leAudioProfile.getActiveDevices().contains(device.getDevice());
+            }
+        }
+
+        return isActiveDeviceA2dp || isActiveDeviceHearingAid || isActiveLeAudio;
     }
 
     private Collection<DeviceCallback> getCallbacks() {
@@ -526,7 +536,7 @@
                 if (cachedDevice != null) {
                     if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                             && !cachedDevice.isConnected()
-                            && isA2dpOrHearingAidDevice(cachedDevice)) {
+                            && isMediaDevice(cachedDevice)) {
                         deviceCount++;
                         cachedBluetoothDeviceList.add(cachedDevice);
                         if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) {
@@ -550,9 +560,10 @@
             return new ArrayList<>(mDisconnectedMediaDevices);
         }
 
-        private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) {
+        private boolean isMediaDevice(CachedBluetoothDevice device) {
             for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
-                if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) {
+                if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
+                        profile instanceof LeAudioProfile) {
                     return true;
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index f21c359..a49d7f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -29,6 +29,7 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -122,6 +123,7 @@
                 break;
             case TYPE_HEARING_AID:
             case TYPE_BLUETOOTH_A2DP:
+            case TYPE_BLE_HEADSET:
                 mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
                 break;
             case TYPE_UNKNOWN:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
index e887c450..f50802a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -52,6 +52,7 @@
 
         when(mDevice.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
         when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+        when(mDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
 
         mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null);
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index f7e0d58..3ccf5e4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -209,8 +209,13 @@
         val heightRatio = state.height.toFloat() / ghostedViewState.height
         val scale = min(widthRatio, heightRatio)
 
+        if (ghostedView.parent is ViewGroup) {
+            // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
+            // view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
+            GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+        }
+
         launchContainer.getLocationOnScreen(launchContainerLocation)
-        GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
         ghostViewMatrix.postScale(
             scale, scale,
             ghostedViewState.centerX - launchContainerLocation[0],
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
index 17b13a2..fa60bc9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -23,14 +23,74 @@
 /**
  * List of {@link Flag} objects for use in SystemUI.
  *
- * Flag Ids are integers. They must be unique.
+ * Flag Ids are integers.
+ * Ids must be unique. This is enforced in a unit test.
+ * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related featurs with
+ * a comment. This is purely for organizational purposes.
  *
  * On public release builds, flags will always return their default value. There is no way to
  * change their value on release builds.
+ *
+ * See {@link FeatureFlagManager} for instructions on flipping the flags via adb.
  */
 public class Flags {
-    public static final BooleanFlag THE_FIRST_FLAG = new BooleanFlag(1, false);
+    public static final BooleanFlag TEAMFOOD = new BooleanFlag(1, false);
 
+    /***************************************/
+    // 100 - notification
+    public static final BooleanFlag NEW_NOTIFICATION_PIPELINE =
+            new BooleanFlag(100, true);
+
+    public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
+            new BooleanFlag(101, false);
+
+    public static final BooleanFlag NOTIFICATION_UPDATES =
+            new BooleanFlag(102, true);
+
+    /***************************************/
+    // 200 - keyguard/lockscreen
+    public static final BooleanFlag KEYGUARD_LAYOUT =
+            new BooleanFlag(200, true);
+
+    public static final BooleanFlag LOCKSCREEN_ANIMATIONS =
+            new BooleanFlag(201, true);
+
+    public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
+            new BooleanFlag(202, true);
+
+    /***************************************/
+    // 300 - power menu
+    public static final BooleanFlag POWER_MENU_LITE =
+            new BooleanFlag(300, true);
+
+    /***************************************/
+    // 400 - smartspace
+    public static final BooleanFlag SMARTSPACE_DEDUPING =
+            new BooleanFlag(400, true);
+
+    public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
+            new BooleanFlag(401, false);
+
+    /***************************************/
+    // 500 - quick settings
+    public static final BooleanFlag NEW_USER_SWITCHER =
+            new BooleanFlag(500, true);
+
+    /***************************************/
+    // 600- status bar
+    public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
+            new BooleanFlag(501, false);
+
+    /***************************************/
+    // 700 - dialer/calls
+    public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
+            new BooleanFlag(600, true);
+
+    public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE =
+            new BooleanFlag(601, true);
+
+    public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
+            new BooleanFlag(602, true);
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index 3761a40..c415ecd 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -21,7 +21,4 @@
     <path
         android:fillColor="@android:color/white"
         android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" />
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M 12 12 C 12.8284271247 12 13.5 12.6715728753 13.5 13.5 C 13.5 14.3284271247 12.8284271247 15 12 15 C 11.1715728753 15 10.5 14.3284271247 10.5 13.5 C 10.5 12.6715728753 11.1715728753 12 12 12 Z" />
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index 720e47b..f91ab6f 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -31,8 +31,7 @@
         android:id="@+id/privacy_dot_left_container"
         android:layout_height="@dimen/status_bar_height"
         android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/status_bar_padding_top"
-        android:layout_marginLeft="0dp"
+        android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_gravity="left|bottom"
         android:visibility="invisible" >
         <ImageView
@@ -51,12 +50,12 @@
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
         android:src="@drawable/rounded_corner_bottom"/>
+
     <FrameLayout
         android:id="@+id/privacy_dot_right_container"
         android:layout_height="@dimen/status_bar_height"
         android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/status_bar_padding_top"
-        android:layout_marginRight="0dp"
+        android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_gravity="right|bottom"
         android:visibility="invisible" >
         <ImageView
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 6abe406..819a9a4e9 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -29,10 +29,9 @@
 
     <FrameLayout
         android:id="@+id/privacy_dot_left_container"
-        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_height="@dimen/status_bar_height"
         android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/status_bar_padding_top"
-        android:layout_marginLeft="0dp"
+        android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_gravity="left|top"
         android:visibility="invisible" >
         <ImageView
@@ -54,10 +53,9 @@
 
     <FrameLayout
         android:id="@+id/privacy_dot_right_container"
-        android:layout_height="@*android:dimen/status_bar_height_portrait"
+        android:layout_height="@dimen/status_bar_height"
         android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/status_bar_padding_top"
-        android:layout_marginRight="0dp"
+        android:paddingTop="@dimen/status_bar_padding_top"
         android:layout_gravity="right|top"
         android:visibility="invisible" >
         <ImageView
@@ -67,8 +65,6 @@
             android:layout_gravity="center_vertical|left"
             android:src="@drawable/system_animation_ongoing_dot"
             android:visibility="visible" />
-
     </FrameLayout>
 
-
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index db63e73..427af68 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> speel tans vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Speel"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Maak <xliff:g id="APP_LABEL">%1$s</xliff:g> oop"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 23bc620..a158d7f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> እየተጫወተ ነው"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ከ<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"አጫውት"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ክፈት"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 7495a49..ae4125b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1114,6 +1114,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"يتم تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> من إجمالي <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"تشغيل"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"فتح <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 43f5758..9da6246 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিং"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ হৈ আছে"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>ৰ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"প্লে’ কৰক"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> খোলক"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 6909e6c..9b4b42b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudulur"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oxudun"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> tətbiqini açın"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 8ec3417..4317599 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se pušta iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pusti"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 42680ea..535144f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"У праграме \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\" прайграецца кампазіцыя \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Прайграць"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Адкрыйце праграму \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 6c1ce79..2f54457 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се възпроизвежда от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> от <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Google Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отваряне на <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index cf8c641..2dc23f0 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চলছে"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"চালান"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> অ্যাপ খুলুন"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 1e05870..0fc1405 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Pjesma <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se reproducira pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pokrenite"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite aplikaciju <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 64c911c..9d8f599 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) s\'està reproduint des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodueix"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Obre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 47b5ff0..1393431 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -716,7 +716,7 @@
     <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Rozšířené ovládací prvky oznámení"</string>
     <string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Zapnuto"</string>
     <string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Vypnuto"</string>
-    <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt z obrazovky uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
+    <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt na obrazovce uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
     <string name="notification_header_default_channel" msgid="225454696914642444">"Oznámení"</string>
     <string name="notification_channel_disabled" msgid="928065923928416337">"Tato oznámení již nebudete dostávat"</string>
     <string name="notification_channel_minimized" msgid="6892672757877552959">"Tato oznámení budou minimalizována"</string>
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> hrajte z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Přehrát"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otevřít aplikaci <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 59c24ce..1502dea 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspilles via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspil"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åbn <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 0bfe97e..d4ebeda 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wird gerade über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergegeben"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Wiedergeben"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> öffnen"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 4b4371b..576a806 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Γίνεται αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> από <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Άνοιγμα της εφαρμογής <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 5ce2897..5a31735 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2eebb52..3898159 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index a667418..1ec39d4 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎Resume‎‏‎‎‏‎"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎Settings‎‏‎‎‏‎"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="SONG_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎<xliff:g id="ARTIST_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ is playing from ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ of ‎‏‎‎‏‏‎<xliff:g id="TOTAL_TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎Play‎‏‎‎‏‎"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎Open ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎Play ‎‏‎‎‏‏‎<xliff:g id="SONG_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎<xliff:g id="ARTIST_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b07038c..e8f8b36cf 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 7a97f1ea..433536a 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -663,8 +663,8 @@
     <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string>
     <string name="overview" msgid="3522318590458536816">"Aplicaciones recientes"</string>
     <string name="demo_mode" msgid="263484519766901593">"Modo de demostración de UI del sistema"</string>
-    <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo de demostración"</string>
-    <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo de demostración"</string>
+    <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo demo"</string>
+    <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo demo"</string>
     <string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string>
     <string name="status_bar_alarm" msgid="87160847643623352">"Alarma"</string>
     <string name="wallet_title" msgid="5369767670735827105">"Wallet"</string>
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index ab33374..e23f50d 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> esitatakse rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Esitamine"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Rakenduse <xliff:g id="APP_LABEL">%1$s</xliff:g> avamine"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 6731ed1..de23069 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) ari da erreproduzitzen <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Erreproduzitu"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ireki <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 69631d4..dd778af 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ازسرگیری"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"تنظیمات"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش می‌شود"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"پخش"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"باز کردن <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index eed2a6c..2ea8dd7 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> soittaa nyt tätä: <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Toista"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Avaa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 2187f77..db02e05c 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecteur à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Faire jouer"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvrez <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index d927847..cd4cfbd 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -895,7 +895,7 @@
     <string name="right_icon" msgid="1103955040645237425">"Icône droite"</string>
     <string name="drag_to_add_tiles" msgid="8933270127508303672">"Faites glisser les blocs pour les ajouter"</string>
     <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Faites glisser les blocs pour les réorganiser"</string>
-    <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les icônes ici pour les supprimer."</string>
+    <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les blocs ici pour les supprimer"</string>
     <string name="drag_to_remove_disabled" msgid="933046987838658850">"Au minimum <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tuiles sont nécessaires"</string>
     <string name="qs_edit" msgid="5583565172803472437">"Modifier"</string>
     <string name="tuner_time" msgid="2450785840990529997">"Heure"</string>
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecture depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Lire"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index a7c1f50..b10090c 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Estase reproducindo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index f63e583..d440222 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચાલી રહ્યું છે"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ચલાવો"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ખોલો"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 1fc0246..0b945f0 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चल रहा है"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> में से <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"चलाएं"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोलें"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f586447..731d00a 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> reproducira se putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodukcija"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvori <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 202ab05..2ebd1458 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című száma hallható itt: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Játék"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> megnyitása"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d359409..735eedd 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Այժմ նվագարկվում է <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>՝ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ից"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Նվագարկել"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Բացեք <xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածը"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 947e8882..cc514c0 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sedang diputar dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> dari <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Putar"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index a643b24..0491d8c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> er í spilun á <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spila"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Opna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index bb77f0c..8aaf77d 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> è in riproduzione da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> di <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Riproduci"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Apri <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 90a2217..9ec4103 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מופעל מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> מתוך <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"הפעלה"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"פתיחה של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 97bf7fb..520b048 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"再開"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)が <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生中"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"再生"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> を開く"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index e1c5b8d..de028b0 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, უკრავს <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-დან <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"დაკვრა"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 8462d00..b1dad23 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әні ойнатылуда."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнату"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> қолданбасын ашу"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index f882906..828c864 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> កំពុងចាក់ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> នៃ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ចាក់"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"បើក <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 47fa557..67d6488 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ಪ್ಲೇ ಮಾಡಿ"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index d1d804a..0f59303 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생 중"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"재생"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 열기"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index d5bbb41..6e4c97f 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ыры (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотулуп жатат"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ичинен <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнотуу"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> колдонмосун ачуу"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index cc6c51d..e61e847 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ກຳລັງຫຼິ້ນຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ຈາກ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ຫຼິ້ນ"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"ເປີດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 91711e0..fc77acb 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ leidžiama iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> iš <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Leisti"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atidaryti „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index f8611ce..bfcb01d 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tiek atskaņots fails “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> no <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Atskaņot"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atveriet lietotni <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index f20b74e..a55fe97 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> е пуштено на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пушти"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворете <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5ac1696..8242d91 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുന്നു"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ൽ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"പ്ലേ ചെയ്യുക"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> തുറക്കുക"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 5d07534..6772aff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулж буй <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-н <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Тоглуулах"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>-г нээх"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 485ed67..2cba8a0 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले होत आहे"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> पैकी <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले करणे"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> उघडा"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b9e583d..461456b 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dimainkan daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> daripada <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Main"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 7f6eccc..35e53a6 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ဆက်လုပ်ရန်"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ဆက်တင်များ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ထားသည်"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> အနက် <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ဖွင့်ခြင်း"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ဖွင့်ပါ"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index fb3c9de..f99aaee 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spilles av fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spill av"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åpne <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index be7a404c..f061507 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बज्दै छ"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> मध्ये <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले गर्नुहोस्"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोल्नुहोस्"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index ed2c45a..756141e 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wordt afgespeeld via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspelen"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> openen"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 69650b2..e336c38 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚାଲୁଛି"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ଚଲାନ୍ତୁ"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 29975a7..cc20ab7 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ਵਿੱਚੋਂ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ਚਲਾਓ"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 987def8..b4fb410 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Aplikacja <xliff:g id="APP_LABEL">%3$s</xliff:g> odtwarza utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Odtwórz"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otwórz aplikację <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 199dbcc..f03525d 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 845de5f..f49627f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Definições"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> em reprodução a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproduzir"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 199dbcc..f03525d 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index d6fc9c0..978c222 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 74283cc..35366df 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Воспроизводится медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\"."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> из <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Воспроизведение"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Открыть приложение \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 33abc73..f6bb208 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"නැවත පටන් ගන්න"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"සැකසීම්"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> ගීතය <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් ධාවනය වෙමින් පවතී"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>කින් <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"වාදනය කරන්න"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> විවෘත කරන්න"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 3c1ddae..c349ef1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sa prehráva z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Prehrať"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvoriť <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 7d60ade..d41459a 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se predvaja iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Predvajaj"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Odpri aplikacijo <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index cc3fdbb..e4ec008 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> po luhet nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> nga <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Luaj"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Hap <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5398dd7..3dbda9b 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1096,6 +1096,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се пушта из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пусти"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворите <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 250f59b..eccf9ee 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spelas upp från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> av <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spela upp"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Öppna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index f4a999c..31f74b2 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> unacheza katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> kati ya <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Cheza"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Fungua <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 6738b8a..f2d2254 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடல் <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேயாகிறது"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"இயக்குதல்"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ஆப்ஸைத் திறங்கள்"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index cce52dd..be277b1 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1090,6 +1090,8 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్‌లు"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి ప్లే అవుతోంది"</string>
+    <!-- no translation found for controls_media_seekbar_description (4389621713616214611) -->
+    <skip />
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ప్లే చేయండి"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ను తెరవండి"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>‌ను ప్లే చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 88ee383..f7811fb 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"กำลังเปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> จาก <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"เล่น"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"เปิด <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 0338702..822a3fd 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Nagpe-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> sa <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"I-play"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buksan ang <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 6017d9d..3849727 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısı çalıyor"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oynat"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> uygulamasını aç"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 82bf387..51cc6dd 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1102,6 +1102,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Пісня \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, грає в додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Відтворення"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Відкрити додаток <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index f41b8aa..0690b5f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چل رہا ہے"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> از <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"چلائیں"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> کھولیں"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index b5e9b4f..acda73d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ijro"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ilovasini ochish"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 4c73df2..2679b00 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Đang phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Phát"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Mở <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 376be92..8ca914d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"打开<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 11e6544..e1c3b35 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在透過 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index a82d751..66434e4 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"系統正透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>,共 <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟「<xliff:g id="APP_LABEL">%1$s</xliff:g>」"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 03448d3..6505503 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1090,6 +1090,7 @@
     <string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string>
     <string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"I-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> idlala kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+    <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ku-<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Dlala"</string>
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Vula i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index db7a0ec..2a70645 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,45 +18,18 @@
 <resources>
     <bool name="are_flags_overrideable">false</bool>
 
-    <bool name="flag_notification_pipeline2">true</bool>
-    <bool name="flag_notification_pipeline2_rendering">false</bool>
-    <bool name="flag_notif_updates">true</bool>
-
     <bool name="flag_monet">true</bool>
 
     <!-- People Tile flag -->
     <bool name="flag_conversations">false</bool>
 
-    <!-- The new animations to/from lockscreen and AOD! -->
-    <bool name="flag_lockscreen_animations">true</bool>
-
-    <!-- The new swipe to unlock animation, which shows the app/launcher behind the keyguard during
-         the swipe. -->
-    <bool name="flag_new_unlock_swipe_animation">true</bool>
-
     <!-- Whether the multi-user icon on the lockscreen opens the QS user detail. If false, clicking
          the multi-user icon will display a list of users directly on the lockscreen. The multi-user
          icon is only shown if config_keyguardUserSwitcher=false in the frameworks config. -->
     <bool name="flag_lockscreen_qs_user_detail_shortcut">false</bool>
 
-    <!-- The shared-element transition between lockscreen smartspace and launcher smartspace. -->
-    <bool name="flag_smartspace_shared_element_transition">false</bool>
-
-    <bool name="flag_pm_lite">true</bool>
-
     <bool name="flag_charging_ripple">false</bool>
 
-    <bool name="flag_ongoing_call_status_bar_chip">true</bool>
-
-    <bool name="flag_ongoing_call_in_immersive">true</bool>
-
-    <bool name="flag_ongoing_call_in_immersive_chip_tap">true</bool>
-
     <bool name="flag_smartspace">false</bool>
 
-    <bool name="flag_smartspace_deduping">true</bool>
-
-    <bool name="flag_combined_status_bar_signal_icons">false</bool>
-
-    <bool name="flag_new_user_switcher">true</bool>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 196e6f3..d447b48 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -118,6 +118,8 @@
     public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
     // The home feature is disabled (either by SUW/SysUI/device policy)
     public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+    // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
+    public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -142,7 +144,8 @@
             SYSUI_STATE_MAGNIFICATION_OVERLAP,
             SYSUI_STATE_IME_SWITCHER_SHOWING,
             SYSUI_STATE_DEVICE_DOZING,
-            SYSUI_STATE_BACK_DISABLED
+            SYSUI_STATE_BACK_DISABLED,
+            SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
     })
     public @interface SystemUiStateFlags {}
 
@@ -174,6 +177,8 @@
         str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : "");
         str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : "");
         str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
+        str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
+                ? "bubbles_mange_menu_expanded" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5c0fb5d..a41a497 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -89,6 +89,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -271,7 +272,6 @@
     private boolean mBouncer; // true if bouncerIsOrWillBeShowing
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
-    private boolean mHasLockscreenWallpaper;
     private boolean mAssistantVisible;
     private boolean mKeyguardOccluded;
     private boolean mOccludingAppRequestingFp;
@@ -314,6 +314,7 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final InteractionJankMonitor mInteractionJankMonitor;
+    private final LatencyTracker mLatencyTracker;
     private boolean mLogoutEnabled;
     // cached value to avoid IPCs
     private boolean mIsUdfpsEnrolled;
@@ -1740,8 +1741,8 @@
             AuthController authController,
             TelephonyListenerManager telephonyListenerManager,
             FeatureFlags featureFlags,
-            InteractionJankMonitor interactionJankMonitor
-    ) {
+            InteractionJankMonitor interactionJankMonitor,
+            LatencyTracker latencyTracker) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -1750,6 +1751,7 @@
         mBackgroundExecutor = backgroundExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
         mInteractionJankMonitor = interactionJankMonitor;
+        mLatencyTracker = latencyTracker;
         mRingerModeTracker = ringerModeTracker;
         mStatusBarStateController = statusBarStateController;
         mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
@@ -2533,31 +2535,6 @@
     }
 
     /**
-     * Update the state whether Keyguard currently has a lockscreen wallpaper.
-     *
-     * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
-     */
-    public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
-        Assert.isMainThread();
-        if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
-            mHasLockscreenWallpaper = hasLockscreenWallpaper;
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-                if (cb != null) {
-                    cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
-                }
-            }
-        }
-    }
-
-    /**
-     * @return Whether Keyguard has a lockscreen wallpaper.
-     */
-    public boolean hasLockscreenWallpaper() {
-        return mHasLockscreenWallpaper;
-    }
-
-    /**
      * Handle {@link #MSG_DPM_STATE_CHANGED}
      */
     private void handleDevicePolicyManagerStateChanged(int userId) {
@@ -2605,6 +2582,7 @@
             }
         }
         mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH);
+        mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 1e951f9..12431984 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,11 +292,6 @@
     public void onStrongAuthStateChanged(int userId) { }
 
     /**
-     * Called when the state whether we have a lockscreen wallpaper has changed.
-     */
-    public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
-
-    /**
      * Called when the dream's window state is changed.
      * @param dreaming true if the dream's window has been created and is visible
      */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 9c10d74..c4147e7 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -161,8 +161,6 @@
     private boolean mPendingRotationChange;
     private boolean mIsRoundedCornerMultipleRadius;
     private boolean mIsPrivacyDotEnabled;
-    private int mStatusBarHeightPortrait;
-    private int mStatusBarHeightLandscape;
     private Drawable mRoundedCornerDrawable;
     private Drawable mRoundedCornerDrawableTop;
     private Drawable mRoundedCornerDrawableBottom;
@@ -315,7 +313,6 @@
 
     private void setupDecorations() {
         if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) {
-            updateStatusBarHeight();
             final DisplayCutout cutout = getCutout();
             for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
                 if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
@@ -326,7 +323,8 @@
                 }
             }
 
-            if (mIsPrivacyDotEnabled) {
+            if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null
+                    && mBottomRightDot != null) {
                 // Overlays have been created, send the dots to the controller
                 //TODO: need a better way to do this
                 mDotViewController.initialize(
@@ -430,7 +428,7 @@
         if (mOverlays[pos] != null) {
             return;
         }
-        mOverlays[pos] = overlayForPosition(pos);
+        mOverlays[pos] = overlayForPosition(pos, cutout);
 
         mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
         ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
@@ -462,10 +460,46 @@
     /**
      * Allow overrides for top/bottom positions
      */
-    private View overlayForPosition(@BoundsPosition int pos) {
+    private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
         final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP)
                 ? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom;
-        return LayoutInflater.from(mContext).inflate(layoutId, null);
+        final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null);
+        initPrivacyDotView(vg, pos, cutout);
+        return vg;
+    }
+
+    private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos,
+            @Nullable DisplayCutout cutout) {
+        final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
+        final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
+        if (!shouldShowPrivacyDot(pos, cutout)) {
+            viewGroup.removeView(left);
+            viewGroup.removeView(right);
+            return;
+        }
+
+        switch (pos) {
+            case BOUNDS_POSITION_LEFT: {
+                mTopLeftDot = left;
+                mBottomLeftDot = right;
+                break;
+            }
+            case BOUNDS_POSITION_TOP: {
+                mTopLeftDot = left;
+                mTopRightDot = right;
+                break;
+            }
+            case BOUNDS_POSITION_RIGHT: {
+                mTopRightDot = left;
+                mBottomRightDot = right;
+                break;
+            }
+            case BOUNDS_POSITION_BOTTOM: {
+                mBottomLeftDot = left;
+                mBottomRightDot = right;
+                break;
+            }
+        }
     }
 
     private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
@@ -483,8 +517,6 @@
         if (mCutoutViews != null && mCutoutViews[pos] != null) {
             mCutoutViews[pos].setRotation(mRotation);
         }
-
-        updatePrivacyDotView(pos, cutout);
     }
 
     @VisibleForTesting
@@ -671,14 +703,6 @@
         }
     }
 
-    private void updateStatusBarHeight() {
-        mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height_landscape);
-        mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height_portrait);
-        mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
-    }
-
     private void updateRoundedCornerRadii() {
         // We should eventually move to just using the intrinsic size of the drawables since
         // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
@@ -813,26 +837,6 @@
         }
     }
 
-    private void updatePrivacyDotView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
-        final ViewGroup viewGroup = (ViewGroup) mOverlays[pos];
-
-        final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
-        final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
-        if (shouldShowPrivacyDot(pos, cutout)) {
-            // TODO (b/201481944) Privacy Dots pos and var are wrong with long side cutout enable
-            if (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP) {
-                mTopLeftDot = left;
-                mTopRightDot = right;
-            } else {
-                mBottomLeftDot = left;
-                mBottomRightDot = right;
-            }
-        } else {
-            viewGroup.removeView(left);
-            viewGroup.removeView(right);
-        }
-    }
-
     private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
         final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
         switch (rotatedPos) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 845d7dc..669965b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -26,6 +26,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -268,6 +269,16 @@
     }
 
     /**
+     * Appends doze updates due to a posture change.
+     */
+    public void tracePostureChanged(
+            @DevicePostureController.DevicePostureInt int posture,
+            String partUpdated
+    ) {
+        mLogger.logPostureChanged(posture, partUpdated);
+    }
+
+    /**
      * Appends pulse dropped event to logs
      */
     public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
@@ -391,8 +402,8 @@
             case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
             case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
             case PULSE_REASON_DOCKING: return "docking";
-            case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "reach-wakelockscreen";
-            case REASON_SENSOR_WAKE_UP: return "presence-wakeup";
+            case PULSE_REASON_SENSOR_WAKE_REACH: return "reach-wakelockscreen";
+            case REASON_SENSOR_WAKE_UP_PRESENCE: return "presence-wakeup";
             case REASON_SENSOR_TAP: return "tap";
             case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
             case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
@@ -403,8 +414,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
             PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
-            PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP,
-            PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP,
+            PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE,
+            PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP,
             REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP})
     public @interface Reason {}
     public static final int PULSE_REASON_NONE = -1;
@@ -415,8 +426,8 @@
     public static final int REASON_SENSOR_DOUBLE_TAP = 4;
     public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
     public static final int PULSE_REASON_DOCKING = 6;
-    public static final int REASON_SENSOR_WAKE_UP = 7;
-    public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8;
+    public static final int REASON_SENSOR_WAKE_UP_PRESENCE = 7;
+    public static final int PULSE_REASON_SENSOR_WAKE_REACH = 8;
     public static final int REASON_SENSOR_TAP = 9;
     public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10;
     public static final int REASON_SENSOR_QUICK_PICKUP = 11;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index dc18618..d79bf22 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.log.LogLevel.ERROR
 import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
+import com.android.systemui.statusbar.policy.DevicePostureController
 import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Locale
@@ -195,6 +196,16 @@
         })
     }
 
+    fun logPostureChanged(posture: Int, partUpdated: String) {
+        buffer.log(TAG, INFO, {
+            int1 = posture
+            str1 = partUpdated
+        }, {
+            "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
+                    " partUpdated=$str1"
+        })
+    }
+
     fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
         buffer.log(TAG, INFO, {
             bool1 = pulsePending
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index da7b389..765c245 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,16 +29,18 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 
-import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.dagger.BrightnessSensor;
 import com.android.systemui.doze.dagger.DozeScope;
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -60,14 +62,17 @@
     private final DozeHost mDozeHost;
     private final Handler mHandler;
     private final SensorManager mSensorManager;
-    private final Optional<Sensor> mLightSensorOptional;
+    private final Optional<Sensor>[] mLightSensorOptional; // light sensors to use per posture
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final DozeParameters mDozeParameters;
-    private final DockManager mDockManager;
+    private final DevicePostureController mDevicePostureController;
+    private final DozeLog mDozeLog;
     private final int[] mSensorToBrightness;
     private final int[] mSensorToScrimOpacity;
     private final int mScreenBrightnessDim;
 
+    @DevicePostureController.DevicePostureInt
+    private int mDevicePosture;
     private boolean mRegistered;
     private int mDefaultDozeBrightness;
     private boolean mPaused = false;
@@ -83,27 +88,36 @@
     private int mDebugBrightnessBucket = -1;
 
     @Inject
-    public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
+    public DozeScreenBrightness(
+            Context context,
+            @WrappedService DozeMachine.Service service,
             AsyncSensorManager sensorManager,
-            @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
+            @BrightnessSensor Optional<Sensor>[] lightSensorOptional,
+            DozeHost host, Handler handler,
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             WakefulnessLifecycle wakefulnessLifecycle,
             DozeParameters dozeParameters,
-            DockManager dockManager) {
+            DevicePostureController devicePostureController,
+            DozeLog dozeLog
+    ) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
         mLightSensorOptional = lightSensorOptional;
+        mDevicePostureController = devicePostureController;
+        mDevicePosture = mDevicePostureController.getDevicePosture();
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mDozeParameters = dozeParameters;
         mDozeHost = host;
         mHandler = handler;
-        mDockManager = dockManager;
+        mDozeLog = dozeLog;
 
         mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
         mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
         mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray;
         mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray;
+
+        mDevicePostureController.addCallback(mDevicePostureCallback);
     }
 
     @Override
@@ -133,6 +147,7 @@
 
     private void onDestroy() {
         setLightSensorEnabled(false);
+        mDevicePostureController.removeCallback(mDevicePostureCallback);
     }
 
     @Override
@@ -159,7 +174,7 @@
             }
 
             int scrimOpacity = -1;
-            if (!mLightSensorOptional.isPresent()) {
+            if (!isLightSensorPresent()) {
                 // No light sensor, scrims are always transparent.
                 scrimOpacity = 0;
             } else if (brightnessReady) {
@@ -172,6 +187,27 @@
         }
     }
 
+    private boolean lightSensorSupportsCurrentPosture() {
+        return mLightSensorOptional != null
+                && mDevicePosture < mLightSensorOptional.length;
+    }
+
+    private boolean isLightSensorPresent() {
+        if (!lightSensorSupportsCurrentPosture()) {
+            return mLightSensorOptional != null && mLightSensorOptional[0].isPresent();
+        }
+
+        return mLightSensorOptional[mDevicePosture].isPresent();
+    }
+
+    private Sensor getLightSensor() {
+        if (!lightSensorSupportsCurrentPosture()) {
+            return null;
+        }
+
+        return mLightSensorOptional[mDevicePosture].get();
+    }
+
     private int computeScrimOpacity(int sensorValue) {
         if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) {
             return -1;
@@ -220,9 +256,9 @@
     }
 
     private void setLightSensorEnabled(boolean enabled) {
-        if (enabled && !mRegistered && mLightSensorOptional.isPresent()) {
+        if (enabled && !mRegistered && isLightSensorPresent()) {
             // Wait until we get an event from the sensor until indicating ready.
-            mRegistered = mSensorManager.registerListener(this, mLightSensorOptional.get(),
+            mRegistered = mSensorManager.registerListener(this, getLightSensor(),
                     SensorManager.SENSOR_DELAY_NORMAL, mHandler);
             mLastSensorValue = -1;
         } else if (!enabled && mRegistered) {
@@ -255,6 +291,41 @@
 
     /** Dump current state */
     public void dump(PrintWriter pw) {
-        pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered);
+        pw.println("DozeScreenBrightness:");
+        IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
+        idpw.increaseIndent();
+        idpw.println("registered=" + mRegistered);
+        idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture));
     }
+
+    private final DevicePostureController.Callback mDevicePostureCallback =
+            new DevicePostureController.Callback() {
+        @Override
+        public void onPostureChanged(int posture) {
+            if (mDevicePosture == posture
+                    || mLightSensorOptional.length < 2
+                    || posture >= mLightSensorOptional.length) {
+                return;
+            }
+
+            final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get();
+            final Sensor newSensor = mLightSensorOptional[posture].get();
+            if (Objects.equals(oldSensor, newSensor)) {
+                mDevicePosture = posture;
+                // uses the same sensor for the new posture
+                return;
+            }
+
+            // cancel the previous sensor:
+            if (mRegistered) {
+                setLightSensorEnabled(false);
+                mDevicePosture = posture;
+                setLightSensorEnabled(true);
+            } else {
+                mDevicePosture = posture;
+            }
+            mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap "
+                    + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 3cefce8..9d0591e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.UiEvent;
@@ -54,10 +55,36 @@
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 
+/**
+ * Tracks and registers/unregisters sensors while the device is dozing based on the config
+ * provided by {@link AmbientDisplayConfiguration} and parameters provided by {@link DozeParameters}
+ *
+ * Sensors registration depends on:
+ *    - sensor existence/availability
+ *    - user configuration (some can be toggled on/off via settings)
+ *    - use of the proximity sensor (sometimes prox cannot be registered in certain display states)
+ *    - touch state
+ *    - device posture
+ *
+ * Sensors will trigger the provided Callback's {@link Callback#onSensorPulse} method.
+ * These sensors include:
+ *    - pickup gesture
+ *    - single and double tap gestures
+ *    - udfps long-press gesture
+ *    - reach and presence gestures
+ *    - quick pickup gesture (low-threshold pickup gesture)
+ *
+ * This class also registers a ProximitySensor that reports near/far events and will
+ * trigger callbacks on the provided {@link mProxCallback}.
+ */
 public class DozeSensors {
 
     private static final boolean DEBUG = DozeService.DEBUG;
@@ -68,15 +95,21 @@
     private final AsyncSensorManager mSensorManager;
     private final AmbientDisplayConfiguration mConfig;
     private final WakeLock mWakeLock;
-    private final Consumer<Boolean> mProxCallback;
+    private final DozeLog mDozeLog;
     private final SecureSettings mSecureSettings;
-    private final Callback mCallback;
+    private final DevicePostureController mDevicePostureController;
     private final boolean mScreenOffUdfpsEnabled;
+
+    // Sensors
     @VisibleForTesting
-    protected TriggerSensor[] mSensors;
+    protected TriggerSensor[] mTriggerSensors;
+    private final ProximitySensor mProximitySensor;
+
+    // Sensor callbacks
+    private final Callback mSensorCallback; // receives callbacks on registered sensor events
+    private final Consumer<Boolean> mProxCallback; // receives callbacks on near/far updates
 
     private final Handler mHandler = new Handler();
-    private final ProximitySensor mProximitySensor;
     private long mDebounceFrom;
     private boolean mSettingRegistered;
     private boolean mListening;
@@ -106,37 +139,47 @@
         }
     }
 
-    DozeSensors(Context context, AsyncSensorManager sensorManager,
-            DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
-            Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
-            ProximitySensor proximitySensor, SecureSettings secureSettings,
+    DozeSensors(
+            Context context,
+            AsyncSensorManager sensorManager,
+            DozeParameters dozeParameters,
+            AmbientDisplayConfiguration config,
+            WakeLock wakeLock,
+            Callback sensorCallback,
+            Consumer<Boolean> proxCallback,
+            DozeLog dozeLog,
+            ProximitySensor proximitySensor,
+            SecureSettings secureSettings,
             AuthController authController,
-            int devicePosture) {
+            DevicePostureController devicePostureController
+    ) {
         mContext = context;
         mSensorManager = sensorManager;
         mConfig = config;
         mWakeLock = wakeLock;
         mProxCallback = proxCallback;
         mSecureSettings = secureSettings;
-        mCallback = callback;
+        mSensorCallback = sensorCallback;
+        mDozeLog = dozeLog;
         mProximitySensor = proximitySensor;
         mProximitySensor.setTag(TAG);
         mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
         mListeningProxSensors = !mSelectivelyRegisterProxSensors;
         mScreenOffUdfpsEnabled =
                 config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
-        mDevicePosture = devicePosture;
+        mDevicePostureController = devicePostureController;
+        mDevicePosture = mDevicePostureController.getDevicePosture();
 
         boolean udfpsEnrolled =
                 authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
         boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
-        mSensors = new TriggerSensor[] {
+        mTriggerSensors = new TriggerSensor[] {
                 new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
                         null /* setting */,
                         dozeParameters.getPulseOnSigMotion(),
                         DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
-                        false /* touchscreen */, dozeLog),
+                        false /* touchscreen */),
                 new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PICK_UP_GESTURE,
@@ -145,18 +188,16 @@
                         DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
                         false /* touchscreen */,
                         false /* ignoresSetting */,
-                        false /* requires prox */,
-                        dozeLog),
+                        false /* requires prox */),
                 new TriggerSensor(
                         findSensor(config.doubleTapSensorType()),
                         Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
                         true /* configured */,
                         DozeLog.REASON_SENSOR_DOUBLE_TAP,
                         dozeParameters.doubleTapReportsTouchCoordinates(),
-                        true /* touchscreen */,
-                        dozeLog),
+                        true /* touchscreen */),
                 new TriggerSensor(
-                        findSensor(config.tapSensorType(mDevicePosture)),
+                        findSensors(config.tapSensorTypeMapping()),
                         Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
                         true /* settingDef */,
                         true /* configured */,
@@ -165,7 +206,7 @@
                         true /* touchscreen */,
                         false /* ignoresSetting */,
                         dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
-                        dozeLog),
+                        mDevicePosture),
                 new TriggerSensor(
                         findSensor(config.longPressSensorType()),
                         Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
@@ -175,8 +216,7 @@
                         true /* reports touch coordinates */,
                         true /* touchscreen */,
                         false /* ignoresSetting */,
-                        dozeParameters.longPressUsesProx() /* requiresProx */,
-                        dozeLog),
+                        dozeParameters.longPressUsesProx() /* requiresProx */),
                 new TriggerSensor(
                         findSensor(config.udfpsLongPressSensorType()),
                         "doze_pulse_on_auth",
@@ -186,25 +226,22 @@
                         true /* reports touch coordinates */,
                         true /* touchscreen */,
                         false /* ignoresSetting */,
-                        dozeParameters.longPressUsesProx(),
-                        dozeLog),
+                        dozeParameters.longPressUsesProx()),
                 new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                         mConfig.wakeScreenGestureAvailable() && alwaysOn,
-                        DozeLog.REASON_SENSOR_WAKE_UP,
+                        DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         false /* reports touch coordinates */,
-                        false /* touchscreen */,
-                        dozeLog),
+                        false /* touchscreen */),
                 new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
                         Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
                         mConfig.wakeScreenGestureAvailable(),
-                        DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN,
+                        DozeLog.PULSE_REASON_SENSOR_WAKE_REACH,
                         false /* reports touch coordinates */,
                         false /* touchscreen */,
-                        mConfig.getWakeLockScreenDebounce(),
-                        dozeLog),
+                        mConfig.getWakeLockScreenDebounce()),
                 new TriggerSensor(
                         findSensor(config.quickPickupSensorType()),
                         Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
@@ -212,10 +249,11 @@
                         config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser())
                                 && udfpsEnrolled,
                         DozeLog.REASON_SENSOR_QUICK_PICKUP,
-                        false /* touchCoords */,
-                        false /* touchscreen */, dozeLog),
+                        false /* requiresTouchCoordinates */,
+                        false /* requiresTouchscreen */,
+                        false /* ignoresSetting */,
+                        false /* requiresProx */),
         };
-
         setProxListening(false);  // Don't immediately start listening when we register.
         mProximitySensor.register(
                 proximityEvent -> {
@@ -223,17 +261,21 @@
                         mProxCallback.accept(!proximityEvent.getBelow());
                     }
                 });
+
+        mDevicePostureController.addCallback(mDevicePostureCallback);
     }
 
     /**
-     *  Unregister any sensors.
+     *  Unregister all sensors and callbacks.
      */
     public void destroy() {
         // Unregisters everything, which is enough to allow gc.
-        for (TriggerSensor triggerSensor : mSensors) {
+        for (TriggerSensor triggerSensor : mTriggerSensors) {
             triggerSensor.setListening(false);
         }
         mProximitySensor.pause();
+
+        mDevicePostureController.removeCallback(mDevicePostureCallback);
     }
 
     /**
@@ -248,6 +290,24 @@
         return findSensor(mSensorManager, type, null);
     }
 
+    @NonNull
+    private Sensor[] findSensors(@NonNull String[] types) {
+        Sensor[] sensorMap = new Sensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+
+        // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
+        // postures
+        Map<String, Sensor> typeToSensorMap = new HashMap<>();
+        for (int i = 0; i < types.length; i++) {
+            String sensorType = types[i];
+            if (!typeToSensorMap.containsKey(sensorType)) {
+                typeToSensorMap.put(sensorType, findSensor(sensorType));
+            }
+            sensorMap[i] = typeToSensorMap.get(sensorType);
+        }
+
+        return sensorMap;
+    }
+
     /**
      * Utility method to find a {@link Sensor} for the supplied string type and string name.
      *
@@ -306,7 +366,7 @@
      */
     private void updateListening() {
         boolean anyListening = false;
-        for (TriggerSensor s : mSensors) {
+        for (TriggerSensor s : mTriggerSensors) {
             boolean listen = mListening
                     && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
                     && (!s.mRequiresProx || mListeningProxSensors);
@@ -319,7 +379,7 @@
         if (!anyListening) {
             mSecureSettings.unregisterContentObserver(mSettingsObserver);
         } else if (!mSettingRegistered) {
-            for (TriggerSensor s : mSensors) {
+            for (TriggerSensor s : mTriggerSensors) {
                 s.registerSettingsObserver(mSettingsObserver);
             }
         }
@@ -328,7 +388,7 @@
 
     /** Set the listening state of only the sensors that require the touchscreen. */
     public void setTouchscreenSensorsListening(boolean listening) {
-        for (TriggerSensor sensor : mSensors) {
+        for (TriggerSensor sensor : mTriggerSensors) {
             if (sensor.mRequiresTouchscreen) {
                 sensor.setListening(listening);
             }
@@ -336,7 +396,7 @@
     }
 
     public void onUserSwitched() {
-        for (TriggerSensor s : mSensors) {
+        for (TriggerSensor s : mTriggerSensors) {
             s.updateListening();
         }
     }
@@ -366,7 +426,7 @@
             if (userId != ActivityManager.getCurrentUser()) {
                 return;
             }
-            for (TriggerSensor s : mSensors) {
+            for (TriggerSensor s : mTriggerSensors) {
                 s.updateListening();
             }
         }
@@ -374,7 +434,7 @@
 
     /** Ignore the setting value of only the sensors that require the touchscreen. */
     public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
-        for (TriggerSensor sensor : mSensors) {
+        for (TriggerSensor sensor : mTriggerSensors) {
             if (sensor.mRequiresTouchscreen) {
                 sensor.ignoreSetting(ignore);
             }
@@ -392,7 +452,7 @@
         pw.println("mScreenOffUdfpsEnabled=" + mScreenOffUdfpsEnabled);
         IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
         idpw.increaseIndent();
-        for (TriggerSensor s : mSensors) {
+        for (TriggerSensor s : mTriggerSensors) {
             idpw.println("Sensor: " + s.toString());
         }
         idpw.println("ProxSensor: " + mProximitySensor.toString());
@@ -407,7 +467,7 @@
 
     @VisibleForTesting
     class TriggerSensor extends TriggerEventListener {
-        final Sensor mSensor;
+        @NonNull final Sensor[] mSensors; // index = posture, value = sensor
         final boolean mConfigured;
         final int mPulseReason;
         private final String mSetting;
@@ -420,27 +480,67 @@
         protected boolean mRegistered;
         protected boolean mDisabled;
         protected boolean mIgnoresSetting;
-        protected final DozeLog mDozeLog;
+        private @DevicePostureController.DevicePostureInt int mPosture;
 
-        public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
-                boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) {
-            this(sensor, setting, true /* settingDef */, configured, pulseReason,
-                    reportsTouchCoordinates, requiresTouchscreen, dozeLog);
+        TriggerSensor(
+                Sensor sensor,
+                String setting,
+                boolean configured,
+                int pulseReason,
+                boolean reportsTouchCoordinates,
+                boolean requiresTouchscreen
+        ) {
+            this(
+                    sensor,
+                    setting,
+                    true /* settingDef */,
+                    configured,
+                    pulseReason,
+                    false /* ignoresSetting */,
+                    false /* requiresProx */,
+                    reportsTouchCoordinates,
+                    requiresTouchscreen
+            );
         }
 
-        public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
-                boolean configured, int pulseReason, boolean reportsTouchCoordinates,
-                boolean requiresTouchscreen, DozeLog dozeLog) {
-            this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
-                    requiresTouchscreen, false /* ignoresSetting */,
-                    false /* requiresProx */, dozeLog);
+        TriggerSensor(
+                Sensor sensor,
+                String setting,
+                boolean settingDef,
+                boolean configured,
+                int pulseReason,
+                boolean reportsTouchCoordinates,
+                boolean requiresTouchscreen,
+                boolean ignoresSetting,
+                boolean requiresProx
+        ) {
+            this(
+                    new Sensor[]{ sensor },
+                    setting,
+                    settingDef,
+                    configured,
+                    pulseReason,
+                    reportsTouchCoordinates,
+                    requiresTouchscreen,
+                    ignoresSetting,
+                    requiresProx,
+                    DevicePostureController.DEVICE_POSTURE_UNKNOWN
+            );
         }
 
-        private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
-                boolean configured, int pulseReason, boolean reportsTouchCoordinates,
-                boolean requiresTouchscreen, boolean ignoresSetting, boolean requiresProx,
-                DozeLog dozeLog) {
-            mSensor = sensor;
+        TriggerSensor(
+                @NonNull Sensor[] sensors,
+                String setting,
+                boolean settingDef,
+                boolean configured,
+                int pulseReason,
+                boolean reportsTouchCoordinates,
+                boolean requiresTouchscreen,
+                boolean ignoresSetting,
+                boolean requiresProx,
+                @DevicePostureController.DevicePostureInt int posture
+        ) {
+            mSensors = sensors;
             mSetting = setting;
             mSettingDefault = settingDef;
             mConfigured = configured;
@@ -449,7 +549,43 @@
             mRequiresTouchscreen = requiresTouchscreen;
             mIgnoresSetting = ignoresSetting;
             mRequiresProx = requiresProx;
-            mDozeLog = dozeLog;
+            mPosture = posture;
+        }
+
+        /**
+         * @return true if the sensor changed based for the new posture
+         */
+        public boolean setPosture(@DevicePostureController.DevicePostureInt int posture) {
+            if (mPosture == posture
+                    || mSensors.length < 2
+                    || posture >= mSensors.length) {
+                return false;
+            }
+
+            Sensor oldSensor = mSensors[mPosture];
+            Sensor newSensor = mSensors[posture];
+            if (Objects.equals(oldSensor, newSensor)) {
+                mPosture = posture;
+                // uses the same sensor for the new posture
+                return false;
+            }
+
+            // cancel the previous sensor:
+            if (mRegistered) {
+                final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
+                if (DEBUG) {
+                    Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
+                            + rt);
+                }
+                mRegistered = false;
+            }
+
+            // update the new sensor:
+            mPosture = posture;
+            updateListening();
+            mDozeLog.tracePostureChanged(mPosture, "DozeSensors swap "
+                    + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+            return true;
         }
 
         public void setListening(boolean listen) {
@@ -471,24 +607,23 @@
         }
 
         public void updateListening() {
-            if (!mConfigured || mSensor == null) return;
+            final Sensor sensor = mSensors[mPosture];
+            if (!mConfigured || sensor == null) return;
             if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
                 if (!mRegistered) {
-                    mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+                    mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
                     if (DEBUG) {
-                        Log.d(TAG, "requestTriggerSensor[" + mSensor
-                                + "] " + mRegistered);
+                        Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
                     }
                 } else {
                     if (DEBUG) {
-                        Log.d(TAG, "requestTriggerSensor[" + mSensor
-                                + "] already registered");
+                        Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
                     }
                 }
             } else if (mRegistered) {
-                final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
+                final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
                 if (DEBUG) {
-                    Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+                    Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
                 }
                 mRegistered = false;
             }
@@ -506,21 +641,29 @@
 
         @Override
         public String toString() {
-            return new StringBuilder("{mRegistered=").append(mRegistered)
+            StringBuilder sb = new StringBuilder();
+            sb.append("{")
+                    .append("mRegistered=").append(mRegistered)
                     .append(", mRequested=").append(mRequested)
                     .append(", mDisabled=").append(mDisabled)
                     .append(", mConfigured=").append(mConfigured)
                     .append(", mIgnoresSetting=").append(mIgnoresSetting)
-                    .append(", mSensor=").append(mSensor).append("}").toString();
+                    .append(", mSensors=").append(Arrays.toString(mSensors));
+            if (mSensors.length > 2) {
+                sb.append(", mPosture=")
+                        .append(DevicePostureController.devicePostureToString(mDevicePosture));
+            }
+            return sb.append("}").toString();
         }
 
         @Override
         @AnyThread
         public void onTrigger(TriggerEvent event) {
+            final Sensor sensor = mSensors[mDevicePosture];
             mDozeLog.traceSensor(mPulseReason);
             mHandler.post(mWakeLock.wrap(() -> {
                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
-                if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
+                if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
                     UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
                 }
 
@@ -531,7 +674,7 @@
                     screenX = event.values[0];
                     screenY = event.values[1];
                 }
-                mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
+                mSensorCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
                 if (!mRegistered) {
                     updateListening();  // reregister, this sensor only fires once
                 }
@@ -569,17 +712,16 @@
         private long mDebounce;
 
         PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
-                int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
-                DozeLog dozeLog) {
+                int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
             this(sensor, setting, configured, pulseReason, reportsTouchCoordinates,
-                    requiresTouchscreen, 0L /* debounce */, dozeLog);
+                    requiresTouchscreen, 0L /* debounce */);
         }
 
         PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
                 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
-                long debounce, DozeLog dozeLog) {
+                long debounce) {
             super(null, setting, configured, pulseReason, reportsTouchCoordinates,
-                    requiresTouchscreen, dozeLog);
+                    requiresTouchscreen);
             mPluginSensor = sensor;
             mDebounce = debounce;
         }
@@ -633,11 +775,22 @@
                     return;
                 }
                 if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
-                mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
+                mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
             }));
         }
     }
 
+    private final DevicePostureController.Callback mDevicePostureCallback = posture -> {
+        if (mDevicePosture == posture) {
+            return;
+        }
+        mDevicePosture = posture;
+
+        for (TriggerSensor triggerSensor : mTriggerSensors) {
+            triggerSensor.setPosture(mDevicePosture);
+        }
+    };
+
     public interface Callback {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b17f078..20cd5b9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -99,8 +99,6 @@
     private final UiEventLogger mUiEventLogger;
     private final DevicePostureController mDevicePostureController;
 
-    private @DevicePostureController.DevicePostureInt int mDevicePosture;
-
     private long mNotificationPulseTime;
     private boolean mPulsePending;
     private Runnable mAodInterruptRunnable;
@@ -197,11 +195,12 @@
         mSensorManager = sensorManager;
         mWakeLock = wakeLock;
         mAllowPulseTriggers = true;
+
         mDevicePostureController = devicePostureController;
-        mDevicePosture = devicePostureController.getDevicePosture();
         mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
-                secureSettings, authController, mDevicePosture);
+                secureSettings, authController, devicePostureController);
+
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
         mProxCheck = proxCheck;
@@ -212,6 +211,10 @@
         mUiEventLogger = uiEventLogger;
         mKeyguardStateController = keyguardStateController;
     }
+    private final DevicePostureController.Callback mDevicePostureCallback =
+            posture -> {
+
+            };
 
     @Override
     public void setDozeMachine(DozeMachine dozeMachine) {
@@ -284,8 +287,8 @@
         boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP;
         boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP;
         boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
-        boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
-        boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
+        boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE;
+        boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH;
         boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
         boolean isQuickPickup = pulseReason == DozeLog.REASON_SENSOR_QUICK_PICKUP;
         boolean isWakeDisplayEvent = isQuickPickup || ((isWakeOnPresence || isWakeOnReach)
@@ -455,7 +458,7 @@
                 mWantSensors = true;
                 mWantTouchScreenSensors = true;
                 if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
-                    onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP);
+                    onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE);
                 }
                 break;
             case DOZE_AOD_PAUSED:
@@ -524,7 +527,7 @@
         // When already pulsing we're allowed to show the wallpaper directly without
         // requesting a new pulse.
         if (dozeState == DozeMachine.State.DOZE_PULSING
-                && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+                && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
             mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT);
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index fbe06b0..374bed3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -155,7 +155,7 @@
                     public void onPulseStarted() {
                         try {
                             mMachine.requestState(
-                                    reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+                                    reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
                                             ? DozeMachine.State.DOZE_PULSING_BRIGHT
                                             : DozeMachine.State.DOZE_PULSING);
                         } catch (IllegalStateException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 571b666..32b7658 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -43,6 +43,9 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 
 import dagger.Module;
@@ -94,16 +97,43 @@
 
     @Provides
     @BrightnessSensor
-    static Optional<Sensor> providesBrightnessSensor(
+    static Optional<Sensor>[] providesBrightnessSensors(
             AsyncSensorManager sensorManager,
             Context context,
-            DozeParameters dozeParameters,
-            DevicePostureController devicePostureController) {
-        return Optional.ofNullable(
-                DozeSensors.findSensor(
-                        sensorManager,
-                        context.getString(R.string.doze_brightness_sensor_type),
-                        dozeParameters.brightnessName(devicePostureController.getDevicePosture())
-                ));
+            DozeParameters dozeParameters) {
+        String[] sensorNames = dozeParameters.brightnessNames();
+        if (sensorNames.length == 0 || sensorNames == null) {
+            // if no brightness names are specified, just use the brightness sensor type
+            return new Optional[]{
+                    Optional.ofNullable(DozeSensors.findSensor(
+                            sensorManager,
+                            context.getString(R.string.doze_brightness_sensor_type),
+                            null
+                    ))
+            };
+        }
+
+        // length and index of brightnessMap correspond to DevicePostureController.DevicePostureInt:
+        final Optional<Sensor>[] brightnessSensorMap =
+                new Optional[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+        Arrays.fill(brightnessSensorMap, Optional.empty());
+
+        // Map of sensorName => Sensor, so we reuse the same sensor if it's the same between
+        // postures
+        Map<String, Optional<Sensor>> nameToSensorMap = new HashMap<>();
+        for (int i = 0; i < sensorNames.length; i++) {
+            final String sensorName = sensorNames[i];
+            if (!nameToSensorMap.containsKey(sensorName)) {
+                nameToSensorMap.put(sensorName,
+                        Optional.ofNullable(
+                                DozeSensors.findSensor(
+                                        sensorManager,
+                                        context.getString(R.string.doze_brightness_sensor_type),
+                                        sensorNames[i]
+                                )));
+            }
+            brightnessSensorMap[i] = nameToSensorMap.get(sensorName);
+        }
+        return brightnessSensorMap;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 2e30e7f..5eff0e6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -33,7 +33,7 @@
 /**
  * Class to manage simple DeviceConfig-based feature flags.
  *
- * See {@link FeatureFlagReader} for instructions on defining and flipping flags.
+ * See {@link Flags} for instructions on defining new flags.
  */
 @SysUISingleton
 public class FeatureFlags {
@@ -87,58 +87,61 @@
     }
 
     public boolean isNewNotifPipelineEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
+        return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE);
     }
 
     public boolean isNewNotifPipelineRenderingEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering);
+        return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING);
     }
 
     /** */
     public boolean useNewLockscreenAnimations() {
-        return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
+        return isEnabled(Flags.LOCKSCREEN_ANIMATIONS);
     }
 
     public boolean isPeopleTileEnabled() {
+        // TODO(b/202860494): different resource overlays have different values.
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
 
     public boolean isMonetEnabled() {
+        // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete.
         return mFlagReader.isEnabled(R.bool.flag_monet);
     }
 
     public boolean isPMLiteEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_pm_lite);
+        return isEnabled(Flags.POWER_MENU_LITE);
     }
 
     public boolean isChargingRippleEnabled() {
+        // TODO(b/202860494): different resource overlays have different values.
         return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
     }
 
     public boolean isOngoingCallStatusBarChipEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
+        return isEnabled(Flags.ONGOING_CALL_STATUS_BAR_CHIP);
     }
 
     public boolean isOngoingCallInImmersiveEnabled() {
-        return isOngoingCallStatusBarChipEnabled()
-                && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
+        return isOngoingCallStatusBarChipEnabled() && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE);
     }
 
     public boolean isOngoingCallInImmersiveChipTapEnabled() {
         return isOngoingCallInImmersiveEnabled()
-                && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive_chip_tap);
+                && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP);
     }
 
     public boolean isSmartspaceEnabled() {
+        // TODO(b/202860494): different resource overlays have different values.
         return mFlagReader.isEnabled(R.bool.flag_smartspace);
     }
 
     public boolean isSmartspaceDedupingEnabled() {
-        return isSmartspaceEnabled() && mFlagReader.isEnabled(R.bool.flag_smartspace_deduping);
+        return isSmartspaceEnabled() && isEnabled(Flags.SMARTSPACE_DEDUPING);
     }
 
     public boolean isNewKeyguardSwipeAnimationEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_new_unlock_swipe_animation);
+        return isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION);
     }
 
     public boolean isKeyguardQsUserDetailsShortcutEnabled() {
@@ -146,12 +149,12 @@
     }
 
     public boolean isSmartSpaceSharedElementTransitionEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_smartspace_shared_element_transition);
+        return isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED);
     }
 
     /** Whether or not to use the provider model behavior for the status bar icons */
     public boolean isCombinedStatusBarSignalIconsEnabled() {
-        return mFlagReader.isEnabled(R.bool.flag_combined_status_bar_signal_icons);
+        return isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS);
     }
 
     /** System setting for provider model behavior */
@@ -163,7 +166,7 @@
      * Use the new version of the user switcher
      */
     public boolean useNewUserSwitcher() {
-        return mFlagReader.isEnabled(R.bool.flag_new_user_switcher);
+        return isEnabled(Flags.NEW_USER_SWITCHER);
     }
 
     /** static method for the system setting */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6fb88bb..1547cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -673,13 +673,6 @@
                 }
             }
         }
-
-        @Override
-        public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
-            synchronized (KeyguardViewMediator.this) {
-                notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
-            }
-        }
     };
 
     ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -2905,21 +2898,6 @@
         }
     }
 
-    private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
-        int size = mKeyguardStateCallbacks.size();
-        for (int i = size - 1; i >= 0; i--) {
-            try {
-                mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged(
-                        hasLockscreenWallpaper);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e);
-                if (e instanceof DeadObjectException) {
-                    mKeyguardStateCallbacks.remove(i);
-                }
-            }
-        }
-    }
-
     public void addStateMonitorCallback(IKeyguardStateCallback callback) {
         synchronized (this) {
             mKeyguardStateCallbacks.add(callback);
@@ -2929,7 +2907,6 @@
                 callback.onInputRestrictedStateChanged(mInputRestricted);
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
                         KeyguardUpdateMonitor.getCurrentUser()));
-                callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper());
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 1000788..6f8a5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -24,7 +24,6 @@
 import android.view.Gravity
 import android.view.View
 import android.widget.FrameLayout
-
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.R
@@ -44,7 +43,6 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
 
-import java.lang.IllegalStateException
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -71,9 +69,6 @@
     private val contentInsetsProvider: StatusBarContentInsetsProvider,
     private val animationScheduler: SystemStatusAnimationScheduler
 ) {
-    private var sbHeightPortrait = 0
-    private var sbHeightLandscape = 0
-
     private lateinit var tl: View
     private lateinit var tr: View
     private lateinit var bl: View
@@ -157,16 +152,12 @@
 
         val newCorner = selectDesignatedCorner(rot, isRtl)
         val index = newCorner.cornerIndex()
+        val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot)
 
-        val h = when (rot) {
-            0, 2 -> sbHeightPortrait
-            1, 3 -> sbHeightLandscape
-            else -> 0
-        }
         synchronized(lock) {
             nextViewState = nextViewState.copy(
                     rotation = rot,
-                    height = h,
+                    paddingTop = paddingTop,
                     designatedCorner = newCorner,
                     cornerIndex = index)
         }
@@ -204,26 +195,17 @@
         }
     }
 
-    @UiThread
-    private fun updateHeights(rot: Int) {
-        val height = when (rot) {
-            0, 2 -> sbHeightPortrait
-            1, 3 -> sbHeightLandscape
-            else -> 0
-        }
-
-        views.forEach { it.layoutParams.height = height }
-    }
-
     // Update the gravity and margins of the privacy views
     @UiThread
-    private fun updateRotations(rotation: Int) {
+    private fun updateRotations(rotation: Int, paddingTop: Int) {
         // To keep a view in the corner, its gravity is always the description of its current corner
         // Therefore, just figure out which view is in which corner. This turns out to be something
         // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
         // rotating the device counter-clockwise increments rotation by 1
 
         views.forEach { corner ->
+            corner.setPadding(0, paddingTop, 0, 0)
+
             val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
             (corner.layoutParams as FrameLayout.LayoutParams).apply {
                 gravity = rotatedCorner.toGravity()
@@ -266,6 +248,7 @@
 
         var rot = activeRotationForCorner(tl, rtl)
         var contentInsets = state.contentRectForRotation(rot)
+        tl.setPadding(0, state.paddingTop, 0, 0)
         (tl.layoutParams as FrameLayout.LayoutParams).apply {
             height = contentInsets.height()
             if (rtl) {
@@ -277,6 +260,7 @@
 
         rot = activeRotationForCorner(tr, rtl)
         contentInsets = state.contentRectForRotation(rot)
+        tr.setPadding(0, state.paddingTop, 0, 0)
         (tr.layoutParams as FrameLayout.LayoutParams).apply {
             height = contentInsets.height()
             if (rtl) {
@@ -288,6 +272,7 @@
 
         rot = activeRotationForCorner(br, rtl)
         contentInsets = state.contentRectForRotation(rot)
+        br.setPadding(0, state.paddingTop, 0, 0)
         (br.layoutParams as FrameLayout.LayoutParams).apply {
             height = contentInsets.height()
             if (rtl) {
@@ -299,6 +284,7 @@
 
         rot = activeRotationForCorner(bl, rtl)
         contentInsets = state.contentRectForRotation(rot)
+        bl.setPadding(0, state.paddingTop, 0, 0)
         (bl.layoutParams as FrameLayout.LayoutParams).apply {
             height = contentInsets.height()
             if (rtl) {
@@ -413,6 +399,7 @@
         val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE)
         val bottom = contentInsetsProvider
                 .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN)
+        val paddingTop = contentInsetsProvider.getStatusBarPaddingTop()
 
         synchronized(lock) {
             nextViewState = nextViewState.copy(
@@ -423,20 +410,12 @@
                     portraitRect = top,
                     landscapeRect = right,
                     upsideDownRect = bottom,
+                    paddingTop = paddingTop,
                     layoutRtl = rtl
             )
         }
     }
 
-    /**
-     * Set the status bar height in portrait and landscape, in pixels. If they are the same you can
-     * pass the same value twice
-     */
-    fun setStatusBarHeights(portrait: Int, landscape: Int) {
-        sbHeightPortrait = portrait
-        sbHeightLandscape = landscape
-    }
-
     private fun updateStatusBarState() {
         synchronized(lock) {
             nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs())
@@ -489,7 +468,7 @@
 
         if (state.rotation != currentViewState.rotation) {
             // A rotation has started, hide the views to avoid flicker
-            updateRotations(state.rotation)
+            updateRotations(state.rotation, state.paddingTop)
         }
 
         if (state.needsLayout(currentViewState)) {
@@ -628,7 +607,7 @@
     val layoutRtl: Boolean = false,
 
     val rotation: Int = 0,
-    val height: Int = 0,
+    val paddingTop: Int = 0,
     val cornerIndex: Int = -1,
     val designatedCorner: View? = null,
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b7d721e..3a37fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -41,8 +41,11 @@
 import android.widget.FrameLayout.LayoutParams;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -305,7 +308,7 @@
         final int showDismissSetting =  Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1);
         final boolean newFlowHideShelf = showDismissSetting == -1
-                ? mContext.getResources().getBoolean(R.bool.flag_notif_updates)
+                ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES)
                 : showDismissSetting == 1;
         if (newFlowHideShelf) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 135b7df..1cb5e62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -21,6 +21,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -39,6 +41,7 @@
     private final ExpandableView[] mLastInSectionViews;
     private final ExpandableView[] mTmpFirstInSectionViews;
     private final ExpandableView[] mTmpLastInSectionViews;
+    private final FeatureFlags mFeatureFlags;
     private boolean mExpanded;
     private HashSet<ExpandableView> mAnimatedChildren;
     private Runnable mRoundingChangedCallback;
@@ -52,7 +55,10 @@
     private ExpandableView mViewAfterSwipedView = null;
 
     @Inject
-    NotificationRoundnessManager(NotificationSectionsFeatureManager sectionsFeatureManager) {
+    NotificationRoundnessManager(
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -118,9 +124,8 @@
     void setViewsAffectedBySwipe(
             ExpandableView viewBefore,
             ExpandableView viewSwiped,
-            ExpandableView viewAfter,
-            boolean cornerAnimationsEnabled) {
-        if (!cornerAnimationsEnabled) {
+            ExpandableView viewAfter) {
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) {
             return;
         }
         final boolean animate = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index aea6e3d..c7f7b66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5298,10 +5298,10 @@
             }
         }
         mController.getNoticationRoundessManager()
-                .setViewsAffectedBySwipe((ExpandableView) viewBefore,
+                .setViewsAffectedBySwipe(
+                        (ExpandableView) viewBefore,
                         (ExpandableView) viewSwiped,
-                        (ExpandableView) viewAfter,
-                        getResources().getBoolean(R.bool.flag_notif_updates));
+                        (ExpandableView) viewAfter);
 
         updateFirstAndLastBackgroundViews();
         requestDisallowInterceptTouchEvent(true);
@@ -5313,8 +5313,7 @@
     void onSwipeEnd() {
         updateFirstAndLastBackgroundViews();
         mController.getNoticationRoundessManager()
-                .setViewsAffectedBySwipe(null, null, null,
-                        getResources().getBoolean(R.bool.flag_notif_updates));
+                .setViewsAffectedBySwipe(null, null, null);
         // Round bottom corners for notification right before shelf.
         mShelf.updateAppearance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5bf982b..49e3fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -283,14 +283,12 @@
     }
 
     /**
-     * Sensor to use for brightness changes.
+     * Gets the brightness string array per posture. Brightness names along with
+     * doze_brightness_sensor_type is used to determine the brightness sensor to use for
+     * the current posture.
      */
-    public String brightnessName(@DevicePostureController.DevicePostureInt int posture) {
-        return AmbientDisplayConfiguration.getSensorFromPostureMapping(
-                mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping),
-                null /* defaultValue */,
-                posture
-        );
+    public String[] brightnessNames() {
+        return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index f289b9f..57b9c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -226,11 +226,11 @@
             return;
         }
 
-        if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+        if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
             mScrimController.setWakeLockScreenSensorActive(true);
         }
 
-        boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+        boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
                         && mWakeLockScreenPerformsAuth;
         // Set the state to pulsing, so ScrimController will know what to do once we ask it to
         // execute the transition. The pulse callback will then be invoked when the scrims
@@ -329,7 +329,7 @@
 
     @Override
     public void extendPulse(int reason) {
-        if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+        if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
             mScrimController.setWakeLockScreenSensorActive(true);
         }
         if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 78fcd82..2a13e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -119,7 +119,6 @@
         LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
         if (result.success) {
             mCached = true;
-            mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
             mCache = result.bitmap;
         }
         return mCache;
@@ -235,7 +234,6 @@
                 if (result.success) {
                     mCached = true;
                     mCache = result.bitmap;
-                    mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
                     mMediaManager.updateMediaMetaData(
                             true /* metaDataChanged */, true /* allowEnterAnimation */);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 98be77d..5bfb236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -179,6 +179,11 @@
                 minRight)
     }
 
+    fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
+        val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources
+        return res.getDimensionPixelSize(R.dimen.status_bar_padding_top)
+    }
+
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         insetsCache.snapshot().forEach { (key, rect) ->
             pw.println("$key -> $rect")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index dadc016..b630689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -59,6 +59,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.systemui.Dumpable;
 import com.android.systemui.GuestResumeSessionReceiver;
@@ -128,6 +129,7 @@
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final IActivityTaskManager mActivityTaskManager;
     private final InteractionJankMonitor mInteractionJankMonitor;
+    private final LatencyTracker mLatencyTracker;
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     @VisibleForTesting
@@ -174,6 +176,7 @@
             SecureSettings secureSettings,
             @Background Executor bgExecutor,
             InteractionJankMonitor interactionJankMonitor,
+            LatencyTracker latencyTracker,
             DumpManager dumpManager) {
         mContext = context;
         mActivityManager = activityManager;
@@ -184,6 +187,7 @@
         mUiEventLogger = uiEventLogger;
         mFalsingManager = falsingManager;
         mInteractionJankMonitor = interactionJankMonitor;
+        mLatencyTracker = latencyTracker;
         mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
                 this, mUserTracker, mUiEventLogger, secureSettings);
         mUserDetailAdapter = userDetailAdapter;
@@ -499,6 +503,7 @@
             mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
                     .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView)
                     .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+            mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
             pauseRefreshUsers();
             mActivityManager.switchUser(id);
         } catch (RemoteException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
index 8dd3d6b..4f45aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
@@ -33,7 +33,14 @@
 
     private inner class TransitionListener : TransitionProgressListener {
         override fun onTransitionProgress(progress: Float) {
-            wallpaperController.setUnfoldTransitionZoom(progress)
+            // Fully zoomed in when fully unfolded
+            wallpaperController.setUnfoldTransitionZoom(1 - progress)
+        }
+
+        override fun onTransitionFinished() {
+            // Resets wallpaper zoom-out to 0f when fully folded
+            // When fully unfolded it is set to 0f by onTransitionProgress
+            wallpaperController.setUnfoldTransitionZoom(0f)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
index 0be6068c..96980b8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -177,9 +178,7 @@
         // length and index of sensorMap correspond to DevicePostureController.DevicePostureInt:
         final ThresholdSensor[] sensorMap =
                 new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
-        for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) {
-            sensorMap[i] = noProxSensor;
-        }
+        Arrays.fill(sensorMap, noProxSensor);
 
         if (!hasPostureSupport(sensorTypes)) {
             Log.e("SensorModule", "config doesn't support postures,"
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index d3581a9..291c64d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -377,10 +377,24 @@
                 sysuiMainExecutor.execute(() -> {
                     sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
                             .commitUpdate(mContext.getDisplayId());
+                    if (!shouldExpand) {
+                        sysUiState.setFlag(
+                                QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+                                false).commitUpdate(mContext.getDisplayId());
+                    }
                 });
             }
 
             @Override
+            public void onManageMenuExpandChanged(boolean menuExpanded) {
+                sysuiMainExecutor.execute(() -> {
+                    sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+                            menuExpanded).commitUpdate(mContext.getDisplayId());
+                });
+            }
+
+
+            @Override
             public void onUnbubbleConversation(String key) {
                 sysuiMainExecutor.execute(() -> {
                     final NotificationEntry entry =
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index c776ab9..74611cc 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
@@ -101,6 +102,7 @@
                     | SYSUI_STATE_BOUNCER_SHOWING
                     | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
                     | SYSUI_STATE_BUBBLES_EXPANDED
+                    | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
                     | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 
     // Shell interfaces
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3edfd03..e53b450 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -79,6 +79,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
@@ -172,6 +173,8 @@
     private FeatureFlags mFeatureFlags;
     @Mock
     private InteractionJankMonitor mInteractionJankMonitor;
+    @Mock
+    private LatencyTracker mLatencyTracker;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
     // Direct executor
@@ -741,7 +744,8 @@
             public void sendResult(Bundle data) {} // do nothing
         };
         mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
-        verify(mInteractionJankMonitor).end(eq(InteractionJankMonitor.CUJ_USER_SWITCH));
+        verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
+        verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
     }
 
     @Test
@@ -1059,7 +1063,7 @@
                     mRingerModeTracker, mBackgroundExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager, mFeatureFlags,
-                    mInteractionJankMonitor);
+                    mInteractionJankMonitor, mLatencyTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 866791c..364b5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -67,7 +67,8 @@
 
         when(config.tapGestureEnabled(anyInt())).thenReturn(true);
         when(config.tapSensorAvailable()).thenReturn(true);
-        when(config.tapSensorType(anyInt())).thenReturn(FakeSensorManager.TAP_SENSOR_TYPE);
+        when(config.tapSensorTypeMapping()).thenReturn(
+                new String[]{FakeSensorManager.TAP_SENSOR_TYPE});
 
         when(config.dozePickupSensorAvailable()).thenReturn(false);
         when(config.wakeScreenGestureAvailable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index e0520b4..886f84e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -60,6 +61,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -76,6 +78,7 @@
 
     private DozeServiceFake mServiceFake;
     private FakeSensorManager.FakeGenericSensor mSensor;
+    private FakeSensorManager.FakeGenericSensor mSensorInner;
     private AsyncSensorManager mSensorManager;
     private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
     @Mock
@@ -86,6 +89,10 @@
     DozeParameters mDozeParameters;
     @Mock
     DockManager mDockManager;
+    @Mock
+    DevicePostureController mDevicePostureController;
+    @Mock
+    DozeLog mDozeLog;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
 
@@ -111,9 +118,19 @@
         mAlwaysOnDisplayPolicy.dimBrightness = DIM_BRIGHTNESS;
         mAlwaysOnDisplayPolicy.dimmingScrimArray = SENSOR_TO_OPACITY;
         mSensor = fakeSensorManager.getFakeLightSensor();
-        mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+        mSensorInner = fakeSensorManager.getFakeLightSensor2();
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[]{Optional.of(mSensor.getSensor())},
+                mDozeHost,
+                null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
     }
 
     @Test
@@ -151,7 +168,7 @@
 
     @Test
     public void doze_doesNotUseLightSensor() {
-        // GIVEN the device is docked and the display state changes to ON
+        // GIVEN the device is DOZE and the display state changes to ON
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         waitForSensorManager();
@@ -166,7 +183,7 @@
 
     @Test
     public void aod_usesLightSensor() {
-        // GIVEN the device is docked and the display state changes to ON
+        // GIVEN the device is DOZE_AOD and the display state changes to ON
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
         waitForSensorManager();
@@ -209,9 +226,17 @@
 
     @Test
     public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
-        mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[] {Optional.empty()} /* sensor */,
+                mDozeHost, null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         reset(mDozeHost);
@@ -238,9 +263,17 @@
 
     @Test
     public void testNullSensor() throws Exception {
-        mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
-                Optional.empty() /* sensor */, mDozeHost, null /* handler */,
-                mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager);
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[]{Optional.empty()} /* sensor */,
+                mDozeHost, null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -249,6 +282,130 @@
     }
 
     @Test
+    public void testSensorsSupportPostures_closed() throws Exception {
+        // GIVEN the device is CLOSED
+        when(mDevicePostureController.getDevicePosture()).thenReturn(
+                DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        // GIVEN closed and opened postures use different light sensors
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[]{
+                        Optional.empty() /* unknown */,
+                        Optional.of(mSensor.getSensor()) /* closed */,
+                        Optional.of(mSensorInner.getSensor()) /* half-opened */,
+                        Optional.of(mSensorInner.getSensor()) /* opened */,
+                        Optional.empty() /* flipped */
+                },
+                mDozeHost, null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
+
+        // GIVEN the device is in AOD
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        waitForSensorManager();
+
+        // WHEN new different events are sent from the inner and outer sensors
+        mSensor.sendSensorEvent(3); // CLOSED sensor
+        mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+        // THEN brightness is updated according to the sensor for CLOSED
+        assertEquals(3, mServiceFake.screenBrightness);
+    }
+
+    @Test
+    public void testSensorsSupportPostures_open() throws Exception {
+        // GIVEN the device is OPENED
+        when(mDevicePostureController.getDevicePosture()).thenReturn(
+                DevicePostureController.DEVICE_POSTURE_OPENED);
+
+        // GIVEN closed and opened postures use different light sensors
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[]{
+                        Optional.empty() /* unknown */,
+                        Optional.of(mSensor.getSensor()) /* closed */,
+                        Optional.of(mSensorInner.getSensor()) /* half-opened */,
+                        Optional.of(mSensorInner.getSensor()) /* opened */,
+                        Optional.empty() /* flipped */
+                },
+                mDozeHost, null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
+
+        // GIVEN device is in AOD
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        waitForSensorManager();
+
+        // WHEN new different events are sent from the inner and outer sensors
+        mSensorInner.sendSensorEvent(4); // OPENED sensor
+        mSensor.sendSensorEvent(3); // CLOSED sensor
+
+        // THEN brightness is updated according to the sensor for OPENED
+        assertEquals(4, mServiceFake.screenBrightness);
+    }
+
+    @Test
+    public void testSensorsSupportPostures_swapPostures() throws Exception {
+        ArgumentCaptor<DevicePostureController.Callback> postureCallbackCaptor =
+                ArgumentCaptor.forClass(DevicePostureController.Callback.class);
+        reset(mDevicePostureController);
+
+        // GIVEN the device starts up AOD OPENED
+        when(mDevicePostureController.getDevicePosture()).thenReturn(
+                DevicePostureController.DEVICE_POSTURE_OPENED);
+
+        // GIVEN closed and opened postures use different light sensors
+        mScreen = new DozeScreenBrightness(
+                mContext,
+                mServiceFake,
+                mSensorManager,
+                new Optional[]{
+                        Optional.empty() /* unknown */,
+                        Optional.of(mSensor.getSensor()) /* closed */,
+                        Optional.of(mSensorInner.getSensor()) /* half-opened */,
+                        Optional.of(mSensorInner.getSensor()) /* opened */,
+                        Optional.empty() /* flipped */
+                },
+                mDozeHost, null /* handler */,
+                mAlwaysOnDisplayPolicy,
+                mWakefulnessLifecycle,
+                mDozeParameters,
+                mDevicePostureController,
+                mDozeLog);
+        verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
+
+        // GIVEN device is in AOD
+        mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+        mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+        waitForSensorManager();
+
+        // WHEN the posture changes to CLOSED
+        postureCallbackCaptor.getValue().onPostureChanged(
+                DevicePostureController.DEVICE_POSTURE_CLOSED);
+        waitForSensorManager();
+
+        // WHEN new different events are sent from the inner and outer sensors
+        mSensor.sendSensorEvent(3); // CLOSED sensor
+        mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+        // THEN brightness is updated according to the sensor for CLOSED
+        assertEquals(3, mServiceFake.screenBrightness);
+    }
+
+    @Test
     public void testNoBrightnessDeliveredAfterFinish() throws Exception {
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 42e34c8..f525fee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -91,9 +91,9 @@
     @Mock
     private AuthController mAuthController;
     @Mock
+    private DevicePostureController mDevicePostureController;
+    @Mock
     private ProximitySensor mProximitySensor;
-    private @DevicePostureController.DevicePostureInt int mDevicePosture =
-            DevicePostureController.DEVICE_POSTURE_UNKNOWN;
     private FakeSettings mFakeSettings = new FakeSettings();
     private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
     private TestableLooper mTestableLooper;
@@ -104,13 +104,14 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+        when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
+                .thenReturn(new String[]{"tapSEnsor"});
         when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
         }).when(mWakeLock).wrap(any(Runnable.class));
-        mDevicePosture = DevicePostureController.DEVICE_POSTURE_UNKNOWN;
         mDozeSensors = new TestableDozeSensors();
     }
 
@@ -127,14 +128,14 @@
 
         mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
         mTestableLooper.processAllMessages();
-        verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+        verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
                 anyFloat(), anyFloat(), eq(null));
 
         mDozeSensors.requestTemporaryDisable();
         reset(mCallback);
         mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
         mTestableLooper.processAllMessages();
-        verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+        verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
                 anyFloat(), anyFloat(), eq(null));
     }
 
@@ -269,15 +270,80 @@
     }
 
     @Test
-    public void testPostureOpen_registersCorrectTapGesture() {
-        // GIVEN device posture open
-        mDevicePosture = DevicePostureController.DEVICE_POSTURE_OPENED;
+    public void testPostureStartStateClosed_registersCorrectSensor() throws Exception {
+        // GIVEN doze sensor that supports postures
+        Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                new Sensor[] {
+                        null /* unknown */,
+                        closedSensor,
+                        null /* half-opened */,
+                        openedSensor},
+                DevicePostureController.DEVICE_POSTURE_CLOSED);
 
-        // WHEN DozeSensors are initialized
-        new TestableDozeSensors();
+        // WHEN trigger sensor requests listening
+        triggerSensor.setListening(true);
 
-        // THEN we use the posture to determine which tap sensor to use
-        verify(mAmbientDisplayConfiguration).tapSensorType(eq(mDevicePosture));
+        // THEN the correct sensor is registered
+        verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+        verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+    }
+
+    @Test
+    public void testPostureChange_registersCorrectSensor() throws Exception {
+        // GIVEN doze sensor that supports postures
+        Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                new Sensor[] {
+                        null /* unknown */,
+                        closedSensor,
+                        null /* half-opened */,
+                        openedSensor},
+                DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        // GIVEN sensor is listening
+        when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+        triggerSensor.setListening(true);
+        reset(mSensorManager);
+        assertTrue(triggerSensor.mRegistered);
+
+        // WHEN posture changes
+        boolean sensorChanged =
+                triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+        // THEN the correct sensor is registered
+        assertTrue(sensorChanged);
+        verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+        verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+    }
+
+    @Test
+    public void testPostureChange_noSensorChange() throws Exception {
+        // GIVEN doze sensor that supports postures
+        Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+                new Sensor[] {
+                        null /* unknown */,
+                        closedSensor,
+                        openedSensor /* half-opened uses the same sensor as opened*/,
+                        openedSensor},
+                DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+
+        // GIVEN sensor is listening
+        when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+        triggerSensor.setListening(true);
+        reset(mSensorManager);
+
+        // WHEN posture changes
+        boolean sensorChanged =
+                triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+        // THEN no change in sensor
+        assertFalse(sensorChanged);
+        verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), any());
     }
 
     @Test
@@ -311,13 +377,12 @@
 
 
     private class TestableDozeSensors extends DozeSensors {
-
         TestableDozeSensors() {
             super(getContext(), mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
-                    mDevicePosture);
-            for (TriggerSensor sensor : mSensors) {
+                    mDevicePostureController);
+            for (TriggerSensor sensor : mTriggerSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
                         == TYPE_WAKE_LOCK_SCREEN) {
@@ -326,7 +391,7 @@
                     mSensorTap = sensor;
                 }
             }
-            mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
+            mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
         }
 
         public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
@@ -337,8 +402,25 @@
                     /* configured */ true,
                     /* pulseReason*/ 0,
                     /* reportsTouchCoordinate*/ false,
-                    requiresTouchScreen,
-                    mDozeLog);
+                    /* requiresTouchscreen */ false,
+                    /* ignoresSetting */ false,
+                    requiresTouchScreen);
+        }
+
+        /**
+         * create a doze sensor that supports postures and is enabled
+         */
+        public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) {
+            return new TriggerSensor(/* sensor */ sensors,
+                    /* setting name */ "test_setting",
+                    /* settingDefault */ true,
+                    /* configured */ true,
+                    /* pulseReason*/ 0,
+                    /* reportsTouchCoordinate*/ false,
+                    /* requiresTouchscreen */ false,
+                    /* ignoresSetting */ true,
+                    /* requiresProx */false,
+                    posture);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 31fa3f8..35dca7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -204,7 +204,7 @@
     public void testProximitySensorNotAvailablel() {
         mProximitySensor.setSensorAvailable(false);
         mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
-        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100,
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH, 100, 100,
                 new float[]{1});
         mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index ac699f7..045e6f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -52,7 +52,7 @@
     private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
     private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
     private boolean mCanBubble = false;
-    private boolean mIsVisuallyInterruptive = false;
+    private boolean mIsTextChanged = false;
     private boolean mIsConversation = false;
     private ShortcutInfo mShortcutInfo = null;
     private int mRankingAdjustment = 0;
@@ -81,7 +81,7 @@
         mSmartActions = copyList(ranking.getSmartActions());
         mSmartReplies = copyList(ranking.getSmartReplies());
         mCanBubble = ranking.canBubble();
-        mIsVisuallyInterruptive = ranking.visuallyInterruptive();
+        mIsTextChanged = ranking.isTextChanged();
         mIsConversation = ranking.isConversation();
         mShortcutInfo = ranking.getConversationShortcutInfo();
         mRankingAdjustment = ranking.getRankingAdjustment();
@@ -110,7 +110,7 @@
                 mSmartActions,
                 mSmartReplies,
                 mCanBubble,
-                mIsVisuallyInterruptive,
+                mIsTextChanged,
                 mIsConversation,
                 mShortcutInfo,
                 mRankingAdjustment,
@@ -189,8 +189,8 @@
         return this;
     }
 
-    public RankingBuilder setVisuallyInterruptive(boolean interruptive) {
-        mIsVisuallyInterruptive = interruptive;
+    public RankingBuilder setTextChanged(boolean textChanged) {
+        mIsTextChanged = textChanged;
         return this;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index d3c1dc9..3c84c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -43,6 +44,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.HashSet;
@@ -57,6 +59,8 @@
     private Runnable mRoundnessCallback = mock(Runnable.class);
     private ExpandableNotificationRow mFirst;
     private ExpandableNotificationRow mSecond;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     private float mSmallRadiusRatio;
 
     @Before
@@ -66,7 +70,8 @@
         mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
                 / resources.getDimension(R.dimen.notification_corner_radius);
         mRoundnessManager = new NotificationRoundnessManager(
-                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
+                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
+                mFeatureFlags);
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 8b5ba38..38d7ce7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -164,7 +164,7 @@
     @Test
     public void testPulseWhileDozing_notifyAuthInterrupt() {
         HashSet<Integer> reasonsWantingAuth = new HashSet<>(
-                Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+                Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH));
         HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
                 Arrays.asList(DozeLog.PULSE_REASON_INTENT,
                         DozeLog.PULSE_REASON_NOTIFICATION,
@@ -173,7 +173,7 @@
                         DozeLog.REASON_SENSOR_DOUBLE_TAP,
                         DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
                         DozeLog.PULSE_REASON_DOCKING,
-                        DozeLog.REASON_SENSOR_WAKE_UP,
+                        DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         DozeLog.REASON_SENSOR_QUICK_PICKUP,
                         DozeLog.REASON_SENSOR_TAP));
         HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index a846b06..5e3b7d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -34,6 +34,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.LatencyTracker
 import com.android.internal.util.UserIcons
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.R
@@ -83,6 +84,7 @@
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock private lateinit var latencyTracker: LatencyTracker
     private lateinit var testableLooper: TestableLooper
     private lateinit var uiBgExecutor: FakeExecutor
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -132,6 +134,7 @@
                 secureSettings,
                 uiBgExecutor,
                 interactionJankMonitor,
+                latencyTracker,
                 dumpManager)
         userSwitcherController.mPauseRefreshUsers = true
 
@@ -156,6 +159,7 @@
         userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
         testableLooper.processAllMessages()
         verify(interactionJankMonitor).begin(any())
+        verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
         verify(activityManager).switchUser(guestInfo.id)
         assertEquals(1, uiEventLogger.numLogs())
         assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
new file mode 100644
index 0000000..c316402
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+    private val listeners = arrayListOf<TransitionProgressListener>()
+
+    override fun destroy() {
+        listeners.clear()
+    }
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        listeners.add(listener)
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        listeners.remove(listener)
+    }
+
+    override fun onTransitionStarted() {
+        listeners.forEach { it.onTransitionStarted() }
+    }
+
+    override fun onTransitionFinished() {
+        listeners.forEach { it.onTransitionFinished() }
+    }
+
+    override fun onTransitionProgress(progress: Float) {
+        listeners.forEach { it.onTransitionProgress(progress) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
new file mode 100644
index 0000000..6ec0251
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -0,0 +1,51 @@
+package com.android.systemui.unfold
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.WallpaperController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.AdditionalMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var wallpaperController: WallpaperController
+
+    private val progressProvider = TestUnfoldTransitionProvider()
+
+    @JvmField
+    @Rule
+    val mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
+
+    @Before
+    fun setup() {
+        unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
+            wallpaperController)
+        unfoldWallpaperController.init()
+    }
+
+    @Test
+    fun onTransitionProgress_zoomsIn() {
+        progressProvider.onTransitionProgress(0.8f)
+
+        verify(wallpaperController).setUnfoldTransitionZoom(eq(0.2f, 0.001f))
+    }
+
+    @Test
+    fun onTransitionFinished_resetsZoom() {
+        progressProvider.onTransitionFinished()
+
+        verify(wallpaperController).setUnfoldTransitionZoom(eq(0f, 0.001f))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 6e73827..197873f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -55,6 +55,7 @@
 
     private final FakeProximitySensor mFakeProximitySensor;
     private final FakeGenericSensor mFakeLightSensor;
+    private final FakeGenericSensor mFakeLightSensor2;
     private final FakeGenericSensor mFakeTapSensor;
     private final FakeGenericSensor[] mSensors;
 
@@ -70,7 +71,8 @@
         mSensors = new FakeGenericSensor[]{
                 mFakeProximitySensor = new FakeProximitySensor(proxSensor),
                 mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
-                mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
+                mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)),
+                mFakeLightSensor2 = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null))
         };
     }
 
@@ -82,6 +84,10 @@
         return mFakeLightSensor;
     }
 
+    public FakeGenericSensor getFakeLightSensor2() {
+        return mFakeLightSensor2;
+    }
+
     public FakeGenericSensor getFakeTapSensor() {
         return mFakeTapSensor;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 7bb2674..e66491e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -123,11 +123,11 @@
 
         Uri uri = getUriFor(name);
         for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
-            observer.dispatchChange(false, List.of(uri), userHandle);
+            observer.dispatchChange(false, List.of(uri), 0, userHandle);
         }
         for (ContentObserver observer :
                 mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
-            observer.dispatchChange(false, List.of(uri), userHandle);
+            observer.dispatchChange(false, List.of(uri), 0, userHandle);
         }
         return true;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 34cae58..f65caee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -86,7 +87,8 @@
 
         mFakeSettings.putString("cat", "hat");
 
-        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+                anyInt());
     }
 
     @Test
@@ -96,7 +98,8 @@
 
         mFakeSettings.putString("cat", "hat");
 
-        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+                anyInt());
     }
 
     @Test
@@ -119,6 +122,18 @@
         mFakeSettings.putString("cat", "hat");
 
         verify(mContentObserver, never()).dispatchChange(
-                anyBoolean(), any(Collection.class), anyInt());
+                anyBoolean(), any(Collection.class), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testContentObserverDispatchCorrectUser() {
+        int user = 10;
+        mFakeSettings.registerContentObserverForUser(
+                mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL
+        );
+
+        mFakeSettings.putStringForUser("cat", "hat", user);
+        verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+                eq(user));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9f755f7..1159e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -179,6 +179,7 @@
 
     private SysUiState mSysUiState;
     private boolean mSysUiStateBubblesExpanded;
+    private boolean mSysUiStateBubblesManageMenuExpanded;
 
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -295,9 +296,13 @@
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
         mSysUiState = new SysUiState();
-        mSysUiState.addCallback(sysUiFlags ->
-                mSysUiStateBubblesExpanded =
-                        (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
+        mSysUiState.addCallback(sysUiFlags -> {
+            mSysUiStateBubblesManageMenuExpanded =
+                    (sysUiFlags
+                            & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+            mSysUiStateBubblesExpanded =
+                    (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+        });
 
         // TODO: Fix
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
@@ -372,8 +377,7 @@
     public void testAddBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -381,7 +385,7 @@
         assertFalse(mBubbleController.hasBubbles());
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -396,7 +400,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
 
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -459,7 +463,7 @@
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 eq(mRow.getSbn()), any(), anyInt());
         assertFalse(mBubbleController.hasBubbles());
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
         assertTrue(mRow.isBubble());
     }
 
@@ -478,7 +482,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
 
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -498,8 +502,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Make sure the notif is suppressed
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -508,8 +511,7 @@
         mBubbleController.collapseStack();
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertStackCollapsed();
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -532,8 +534,7 @@
         assertStackExpanded();
         verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
                 true, mRow2.getKey());
-
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -557,8 +558,7 @@
         // Collapse
         mBubbleController.collapseStack();
         assertStackCollapsed();
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -578,8 +578,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -604,8 +603,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -634,7 +632,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -666,7 +664,7 @@
         assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -679,7 +677,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
 
@@ -694,7 +692,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -711,8 +709,7 @@
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
                 mRow.getKey());
         assertStackCollapsed();
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -728,8 +725,7 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
                 mRow.getKey());
         assertStackExpanded();
-
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -746,8 +742,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -769,8 +764,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
-        assertFalse(mSysUiStateBubblesExpanded);
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -784,7 +778,7 @@
 
         mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
 
-        assertTrue(mSysUiStateBubblesExpanded);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -1206,6 +1200,63 @@
         assertNotNull(info);
     }
 
+    @Test
+    public void testShowManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+    }
+
+    @Test
+    public void testHideManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+        // Hide the menu
+        stackView.showManageMenu(false);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+    }
+
+    @Test
+    public void testCollapseBubbleManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+        // Collapse the stack
+        mBubbleData.setExpanded(false);
+
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
@@ -1303,4 +1354,12 @@
         assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
                 entry.getKey(), entry.getGroupKey()));
     }
+
+    /**
+     * Asserts that the system ui states associated to bubbles are in the correct state.
+     */
+    private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+        assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+        assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index a3bbb26..05c4822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -68,6 +68,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -160,7 +161,9 @@
     @Mock
     private AuthController mAuthController;
 
-    private SysUiState mSysUiState = new SysUiState();
+    private SysUiState mSysUiState;
+    private boolean mSysUiStateBubblesExpanded;
+    private boolean mSysUiStateBubblesManageMenuExpanded;
 
     @Captor
     private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
@@ -257,6 +260,15 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
+        mSysUiState = new SysUiState();
+        mSysUiState.addCallback(sysUiFlags -> {
+            mSysUiStateBubblesManageMenuExpanded =
+                    (sysUiFlags
+                            & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+            mSysUiStateBubblesExpanded =
+                    (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+        });
+
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
         mPositioner.setMaxBubbles(5);
         mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -325,6 +337,7 @@
     public void testAddBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -332,6 +345,7 @@
         assertFalse(mBubbleController.hasBubbles());
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -345,6 +359,8 @@
                 mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
+
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -407,6 +423,8 @@
         verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
+
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -425,6 +443,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Make sure the notif is suppressed
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -433,6 +452,7 @@
         mBubbleController.collapseStack();
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertStackCollapsed();
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -455,6 +475,7 @@
         assertStackExpanded();
         verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
                 true, mRow2.getKey());
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -479,6 +500,7 @@
         // Collapse
         mBubbleController.collapseStack();
         assertStackCollapsed();
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -498,6 +520,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -522,6 +545,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -550,6 +574,8 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
 
@@ -580,6 +606,7 @@
         assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
         assertTrue(mBubbleController.hasBubbles());
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -592,6 +619,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
 
@@ -606,6 +634,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
 
@@ -623,6 +652,7 @@
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
                 mRow.getKey());
         assertStackCollapsed();
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -638,6 +668,7 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
                 mRow.getKey());
         assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -654,6 +685,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -675,6 +707,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
@@ -980,6 +1013,63 @@
         verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
     }
 
+    @Test
+    public void testShowManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+    }
+
+    @Test
+    public void testHideManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+        // Hide the menu
+        stackView.showManageMenu(false);
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+    }
+
+    @Test
+    public void testCollapseBubbleManageMenuChangesSysuiState() {
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        // Expand the stack
+        BubbleStackView stackView = mBubbleController.getStackView();
+        mBubbleData.setExpanded(true);
+        assertStackExpanded();
+        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+        // Show the menu
+        stackView.showManageMenu(true);
+        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+        // Collapse the stack
+        mBubbleData.setExpanded(false);
+
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+    }
+
     /**
      * Sets the bubble metadata flags for this entry. These flags are normally set by
      * NotificationManagerService when the notification is sent, however, these tests do not
@@ -1034,4 +1124,12 @@
         assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
                 entry.getKey(), entry.getGroupKey()));
     }
+
+    /**
+     * Asserts that the system ui states associated to bubbles are in the correct state.
+     */
+    private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+        assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+        assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aff7eb2..8205d35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2575,7 +2575,8 @@
         final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
                 || userState.isDisplayMagnificationEnabledLocked())
                 && (userState.getMagnificationCapabilitiesLocked()
-                != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+                != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
+                || userHasMagnificationServicesLocked(userState);
         getWindowMagnificationMgr().requestConnection(connect);
     }
 
@@ -3564,7 +3565,12 @@
             }
         }
 
-        ArrayList<Display> getValidDisplayList() {
+        /**
+         * Gets all currently valid logical displays.
+         *
+         * @return An array list containing all valid logical displays.
+         */
+        public ArrayList<Display> getValidDisplayList() {
             synchronized (mLock) {
                 return mDisplaysList;
             }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 427e90a..4cb786a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -22,6 +22,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothLeAudio;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -503,6 +504,18 @@
         }
     }
 
+    /*package*/ static final class BleVolumeInfo {
+        final int mIndex;
+        final int mMaxIndex;
+        final int mStreamType;
+
+        BleVolumeInfo(int index, int maxIndex, int streamType) {
+            mIndex = index;
+            mMaxIndex = maxIndex;
+            mStreamType = streamType;
+        }
+    };
+
     /*package*/ static final class BtDeviceConnectionInfo {
         final @NonNull BluetoothDevice mDevice;
         final @AudioService.BtProfileConnectionState int mState;
@@ -711,6 +724,11 @@
         sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
     }
 
+     /*package*/ void postSetLeAudioVolumeIndex(int index, int maxIndex, int streamType) {
+        BleVolumeInfo info = new BleVolumeInfo(index, maxIndex, streamType);
+        sendLMsgNoDelay(MSG_II_SET_LE_AUDIO_OUT_VOLUME, SENDMSG_REPLACE, info);
+    }
+
     /*package*/ void postSetModeOwnerPid(int pid, int mode) {
         sendIIMsgNoDelay(MSG_I_SET_MODE_OWNER_PID, SENDMSG_REPLACE, pid, mode);
     }
@@ -851,6 +869,10 @@
         return mAudioService.getVssVolumeForDevice(streamType, device);
     }
 
+    /*package*/ int getMaxVssVolumeForStream(int streamType) {
+        return mAudioService.getMaxVssVolumeForStream(streamType);
+    }
+
     /*package*/ int getDeviceForStream(int streamType) {
         return mAudioService.getDeviceForStream(streamType);
     }
@@ -962,6 +984,10 @@
         sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
     }
 
+    /*package*/ void postDisconnectLeAudio() {
+        sendMsgNoDelay(MSG_DISCONNECT_BT_LE_AUDIO, SENDMSG_QUEUE);
+    }
+
     /*package*/ void postDisconnectHeadset() {
         sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
     }
@@ -983,6 +1009,11 @@
                 hearingAidProfile);
     }
 
+    /*package*/ void postBtLeAudioProfileConnected(BluetoothLeAudio leAudioProfile) {
+        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO, SENDMSG_QUEUE,
+                leAudioProfile);
+    }
+
     /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) {
         sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
     }
@@ -1321,6 +1352,12 @@
                         mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
                     }
                     break;
+                case MSG_II_SET_LE_AUDIO_OUT_VOLUME: {
+                    final BleVolumeInfo info = (BleVolumeInfo) msg.obj;
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setLeAudioVolume(info.mIndex, info.mMaxIndex, info.mStreamType);
+                    }
+                } break;
                 case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
                     synchronized (mDeviceStateLock) {
                         mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
@@ -1384,6 +1421,11 @@
                         }
                     }
                     break;
+                case MSG_DISCONNECT_BT_LE_AUDIO:
+                    synchronized(mDeviceStateLock) {
+                        mDeviceInventory.disconnectLeAudio();
+                    }
+                    break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
                     synchronized (mDeviceStateLock) {
                         mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
@@ -1399,6 +1441,12 @@
                         mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
                     }
                     break;
+
+                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO:
+                    synchronized(mDeviceStateLock) {
+                        mBtHelper.onLeAudioProfileConnected((BluetoothLeAudio) msg.obj);
+                    }
+                    break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
@@ -1586,6 +1634,11 @@
     private static final int MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE = 43;
     private static final int MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT = 44;
     private static final int MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT = 45;
+    // process set volume for Le Audio, obj is BleVolumeInfo
+    private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46;
+
+    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO = 47;
+    private static final int MSG_DISCONNECT_BT_LE_AUDIO = 48;
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 64e620e..6c3c736 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -887,6 +887,28 @@
         }
     }
 
+     /*package*/ void disconnectLeAudio() {
+        synchronized (mDevicesLock) {
+            final ArraySet<String> toRemove = new ArraySet<>();
+            // Disconnect ALL DEVICE_OUT_BLE_HEADSET devices
+            mConnectedDevices.values().forEach(deviceInfo -> {
+                if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLE_HEADSET) {
+                    toRemove.add(deviceInfo.mDeviceAddress);
+                }
+            });
+            new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
+                    .record();
+            if (toRemove.size() > 0) {
+                final int delay = checkSendBecomingNoisyIntentInt(
+                        AudioSystem.DEVICE_OUT_BLE_HEADSET, 0, AudioSystem.DEVICE_NONE);
+                toRemove.stream().forEach(deviceAddress ->
+                        makeLeAudioDeviceUnavailable(deviceAddress,
+                            AudioSystem.DEVICE_OUT_BLE_HEADSET)
+                );
+            }
+        }
+    }
+
     // must be called before removing the device from mConnectedDevices
     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
     // from AudioSystem
@@ -1195,6 +1217,10 @@
             return;
         }
 
+        final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
+                                    AudioSystem.DEVICE_OUT_BLE_HEADSET);
+        final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
+        mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
     }
 
@@ -1243,6 +1269,7 @@
         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE);
         BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
+        BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET);
         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
         BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e8b0e08..bc30f87 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -345,6 +345,10 @@
         return mStreamStates[stream].getIndex(device);
     }
 
+    /*package*/ int getMaxVssVolumeForStream(int stream) {
+        return mStreamStates[stream].getMaxIndex();
+    }
+
     private SettingsObserver mSettingsObserver;
 
     private AtomicInteger mMode = new AtomicInteger(AudioSystem.MODE_NORMAL);
@@ -2991,6 +2995,16 @@
                 mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
             }
 
+            if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
+                    && streamType == getBluetoothContextualVolumeStream()) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+                            + newIndex + " stream=" + streamType);
+                }
+                mDeviceBroker.postSetLeAudioVolumeIndex(newIndex,
+                    mStreamStates[streamType].getMaxIndex(), streamType);
+            }
+
             // Check if volume update should be send to Hearing Aid
             if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
                 // only modify the hearing aid attenuation when the stream to modify matches
@@ -3609,6 +3623,16 @@
                 mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
             }
 
+            if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
+                    && streamType == getBluetoothContextualVolumeStream()) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+                            + index + " stream=" + streamType);
+                }
+                mDeviceBroker.postSetLeAudioVolumeIndex(index,
+                    mStreamStates[streamType].getMaxIndex(), streamType);
+            }
+
             if (device == AudioSystem.DEVICE_OUT_HEARING_AID
                     && streamType == getBluetoothContextualVolumeStream()) {
                 Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 0eb5a5d..3137fa5 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -155,6 +155,7 @@
         static final int VOL_MODE_CHANGE_HEARING_AID = 7;
         static final int VOL_SET_GROUP_VOL = 8;
         static final int VOL_MUTE_STREAM_INT = 9;
+        static final int VOL_SET_LE_AUDIO_VOL = 10;
 
         final int mOp;
         final int mStream;
@@ -310,6 +311,13 @@
                             .set(MediaMetrics.Property.INDEX, mVal1)
                             .record();
                     return;
+                case VOL_SET_LE_AUDIO_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.EVENT, "setLeAudioVolume")
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .set(MediaMetrics.Property.MAX_INDEX, mVal2)
+                            .record();
+                    return;
                 case VOL_SET_AVRCP_VOL:
                     new MediaMetrics.Item(mMetricsId)
                             .set(MediaMetrics.Property.EVENT, "setAvrcpVolume")
@@ -382,6 +390,11 @@
                             .append(" index:").append(mVal1)
                             .append(" gain dB:").append(mVal2)
                             .toString();
+                case VOL_SET_LE_AUDIO_VOL:
+                    return new StringBuilder("setLeAudioVolume:")
+                            .append(" index:").append(mVal1)
+                            .append(" gain dB:").append(mVal2)
+                            .toString();
                 case VOL_SET_AVRCP_VOL:
                     return new StringBuilder("setAvrcpVolume:")
                             .append(" index:").append(mVal1)
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 52e8edf..c924fde 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -25,6 +25,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
@@ -63,6 +64,8 @@
 
     private @Nullable BluetoothHearingAid mHearingAid;
 
+    private @Nullable BluetoothLeAudio mLeAudio;
+
     // Reference to BluetoothA2dp to query for AbsoluteVolume.
     private @Nullable BluetoothA2dp mA2dp;
 
@@ -106,6 +109,8 @@
     private static final int SCO_MODE_MAX = 2;
 
     private static final int BT_HEARING_AID_GAIN_MIN = -128;
+    private static final int BT_LE_AUDIO_MIN_VOL = 0;
+    private static final int BT_LE_AUDIO_MAX_VOL = 255;
 
     /**
      * Returns a string representation of the scoAudioMode.
@@ -235,6 +240,8 @@
                     mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
             adapter.getProfileProxy(mDeviceBroker.getContext(),
                     mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.LE_AUDIO);
         }
     }
 
@@ -389,6 +396,26 @@
         return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
     }
 
+    /*package*/ synchronized void setLeAudioVolume(int index, int maxIndex, int streamType) {
+        if (mLeAudio == null) {
+            if (AudioService.DEBUG_VOL) {
+                Log.i(TAG, "setLeAudioVolume: null mLeAudio");
+            }
+            return;
+        }
+        /* leaudio expect volume value in range 0 to 255
+         */
+        int volume = (index * (BT_LE_AUDIO_MAX_VOL - BT_LE_AUDIO_MIN_VOL)) / maxIndex ;
+
+        if (AudioService.DEBUG_VOL) {
+            Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
+                    + index + " volume=" + volume);
+        }
+        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
+        mLeAudio.setVolume(volume);
+    }
+
     /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
         if (mHearingAid == null) {
             if (AudioService.DEBUG_VOL) {
@@ -428,6 +455,7 @@
         mDeviceBroker.postDisconnectA2dpSink();
         mDeviceBroker.postDisconnectHeadset();
         mDeviceBroker.postDisconnectHearingAid();
+        mDeviceBroker.postDisconnectLeAudio();
     }
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@@ -488,6 +516,23 @@
                 /*eventSource*/ "mBluetoothProfileServiceListener");
     }
 
+    /*package*/ synchronized void onLeAudioProfileConnected(BluetoothLeAudio leAudio) {
+        mLeAudio = leAudio;
+        final List<BluetoothDevice> deviceList = mLeAudio.getConnectedDevices();
+        if (deviceList.isEmpty()) {
+            return;
+        }
+
+        final BluetoothDevice btDevice = deviceList.get(0);
+        final @BluetoothProfile.BtProfileState int state =
+        mLeAudio.getConnectionState(btDevice);
+        mDeviceBroker.postBluetoothLeAudioOutDeviceConnectionState(
+                btDevice, state,
+                /*suppressNoisyIntent*/ false,
+                /*musicDevice android.media.AudioSystem.DEVICE_NONE,*/
+                /*eventSource*/ "mBluetoothProfileServiceListener");
+    }
+
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
@@ -655,6 +700,13 @@
                             mDeviceBroker.postBtHearingAidProfileConnected(
                                     (BluetoothHearingAid) proxy);
                             break;
+
+                        case BluetoothProfile.LE_AUDIO:
+                            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                                    "BT profile service: connecting LE_AUDIO profile"));
+                            mDeviceBroker.postBtLeAudioProfileConnected(
+                                    (BluetoothLeAudio) proxy);
+                            break;
                         default:
                             break;
                     }
@@ -677,6 +729,9 @@
                         case BluetoothProfile.HEARING_AID:
                             mDeviceBroker.postDisconnectHearingAid();
                             break;
+                        case BluetoothProfile.LE_AUDIO:
+                            mDeviceBroker.postDisconnectLeAudio();
+                            break;
 
                         default:
                             break;
@@ -899,6 +954,7 @@
         pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
         pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
         pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
+        pw.println("\n" + prefix + "mLeAudio: " + mLeAudio);
         pw.println(prefix + "mA2dp: " + mA2dp);
         pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
     }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index cfd0a2d..f4aa88f 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1625,7 +1625,7 @@
             Slog.v(TAG, "scheduling sync operation " + syncOperation.toString());
         }
 
-        int priority = syncOperation.findPriority();
+        int bias = syncOperation.getJobBias();
 
         final int networkType = syncOperation.isNotAllowedOnMetered() ?
                 JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;
@@ -1641,7 +1641,7 @@
                 .setRequiredNetworkType(networkType)
                 .setRequiresStorageNotLow(true)
                 .setPersisted(true)
-                .setPriority(priority)
+                .setBias(bias)
                 .setFlags(jobFlags);
 
         if (syncOperation.isPeriodic) {
@@ -3228,7 +3228,7 @@
                 if (asc.mSyncOperation.isConflict(op)) {
                     // If the provided SyncOperation conflicts with a running one, the lower
                     // priority sync is pre-empted.
-                    if (asc.mSyncOperation.findPriority() >= op.findPriority()) {
+                    if (asc.mSyncOperation.getJobBias() >= op.getJobBias()) {
                         if (isLoggable) {
                             Slog.v(TAG, "Rescheduling sync due to conflict " + op.toString());
                         }
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index f6fad25..64b17e5 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -358,13 +358,13 @@
         return sourcePeriodicId != NO_JOB_ID;
     }
 
-    int findPriority() {
+    int getJobBias() {
         if (isInitialization()) {
-            return JobInfo.PRIORITY_SYNC_INITIALIZATION;
+            return JobInfo.BIAS_SYNC_INITIALIZATION;
         } else if (isExpedited()) {
-            return JobInfo.PRIORITY_SYNC_EXPEDITED;
+            return JobInfo.BIAS_SYNC_EXPEDITED;
         }
-        return JobInfo.PRIORITY_DEFAULT;
+        return JobInfo.BIAS_DEFAULT;
     }
 
     private String toKey() {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 7afa81a..73de0f8 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -26,6 +26,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -56,6 +57,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
+    private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     // Maps hardware address to BluetoothRouteInfo
@@ -66,6 +68,8 @@
     BluetoothA2dp mA2dpProfile;
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     BluetoothHearingAid mHearingAidProfile;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothLeAudio mLeAudioProfile;
 
     // Route type -> volume map
     private final SparseIntArray mVolumeMap = new SparseIntArray();
@@ -108,6 +112,7 @@
     public void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
 
         // Bluetooth on/off broadcasts
         addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
@@ -119,6 +124,10 @@
                 deviceStateChangedReceiver);
         addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
                 deviceStateChangedReceiver);
+        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED,
+                deviceStateChangedReceiver);
+        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED,
+                deviceStateChangedReceiver);
 
         mContext.registerReceiverAsUser(mBroadcastReceiver, user,
                 mIntentFilter, null, null);
@@ -240,6 +249,8 @@
                 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
                 | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
             routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+        } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
+            routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
         } else {
             return false;
         }
@@ -288,6 +299,12 @@
             routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device);
             type = MediaRoute2Info.TYPE_HEARING_AID;
         }
+        if (mLeAudioProfile != null
+                && mLeAudioProfile.getConnectedDevices().contains(device)) {
+            newBtRoute.connectedProfiles.put(BluetoothProfile.LE_AUDIO, true);
+            routeId = LE_AUDIO_ROUTE_ID_PREFIX + mLeAudioProfile.getGroupId(device);
+            type = MediaRoute2Info.TYPE_BLE_HEADSET;
+        }
 
         // Current volume will be set when connected.
         newBtRoute.route = new MediaRoute2Info.Builder(routeId, deviceName)
@@ -358,11 +375,7 @@
         }
     }
 
-    private void addActiveHearingAidDevices(BluetoothDevice device) {
-        if (DEBUG) {
-            Log.d(TAG, "Setting active hearing aid devices. device=" + device);
-        }
-
+    private void addActiveDevices(BluetoothDevice device) {
         // Let the given device be the first active device
         BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
         addActiveRoute(activeBtRoute);
@@ -376,6 +389,21 @@
             }
         }
     }
+    private void addActiveHearingAidDevices(BluetoothDevice device) {
+        if (DEBUG) {
+            Log.d(TAG, "Setting active hearing aid devices. device=" + device);
+        }
+
+        addActiveDevices(device);
+    }
+
+    private void addActiveLeAudioDevices(BluetoothDevice device) {
+        if (DEBUG) {
+            Log.d(TAG, "Setting active le audio devices. device=" + device);
+        }
+
+        addActiveDevices(device);
+    }
 
     interface BluetoothRoutesUpdatedListener {
         void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
@@ -392,6 +420,11 @@
             if (connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
                 return MediaRoute2Info.TYPE_HEARING_AID;
             }
+
+            if (connectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
+                return MediaRoute2Info.TYPE_BLE_HEADSET;
+            }
+
             return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
         }
     }
@@ -410,6 +443,10 @@
                     mHearingAidProfile = (BluetoothHearingAid) proxy;
                     activeDevices = mHearingAidProfile.getActiveDevices();
                     break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudioProfile = (BluetoothLeAudio) proxy;
+                    activeDevices = mLeAudioProfile.getActiveDevices();
+                    break;
                 default:
                     return;
             }
@@ -434,6 +471,9 @@
                 case BluetoothProfile.HEARING_AID:
                     mHearingAidProfile = null;
                     break;
+                case BluetoothProfile.LE_AUDIO:
+                    mLeAudioProfile = null;
+                    break;
                 default:
                     return;
             }
@@ -490,12 +530,22 @@
                     }
                     notifyBluetoothRoutesUpdated();
                     break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
+                    clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLE_HEADSET);
+                    if (device != null) {
+                        addActiveLeAudioDevices(device);
+                    }
+                    notifyBluetoothRoutesUpdated();
+                    break;
                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                     handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
                     break;
                 case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
                     handleConnectionStateChanged(BluetoothProfile.HEARING_AID, intent, device);
                     break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.LE_AUDIO, intent, device);
+                    break;
             }
         }
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 654b17f..b45d87f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -39,6 +39,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.am.ProcessList;
+import com.android.server.net.NetworkPolicyManagerService.UidBlockedState;
 
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -72,16 +73,6 @@
     private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
     private static final int EVENT_APP_IDLE_WL_CHANGED = 14;
 
-    static final int NTWK_BLOCKED_POWER = 0;
-    static final int NTWK_ALLOWED_NON_METERED = 1;
-    static final int NTWK_BLOCKED_DENYLIST = 2;
-    static final int NTWK_ALLOWED_ALLOWLIST = 3;
-    static final int NTWK_ALLOWED_TMP_ALLOWLIST = 4;
-    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
-    static final int NTWK_ALLOWED_DEFAULT = 6;
-    static final int NTWK_ALLOWED_SYSTEM = 7;
-    static final int NTWK_BLOCKED_RESTRICTED_MODE = 8;
-
     private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
     private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
     private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -90,12 +81,13 @@
 
     private final Object mLock = new Object();
 
-    void networkBlocked(int uid, int reason) {
+    void networkBlocked(int uid, UidBlockedState uidBlockedState) {
         synchronized (mLock) {
             if (LOGD || uid == mDebugUid) {
-                Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+                Slog.d(TAG, "Blocked state of uid: " + uidBlockedState.toString());
             }
-            mNetworkBlockedBuffer.networkBlocked(uid, reason);
+            mNetworkBlockedBuffer.networkBlocked(uid, uidBlockedState.blockedReasons,
+                    uidBlockedState.allowedReasons, uidBlockedState.effectiveBlockedReasons);
         }
     }
 
@@ -269,29 +261,6 @@
         }
     }
 
-    private static String getBlockedReason(int reason) {
-        switch (reason) {
-            case NTWK_BLOCKED_POWER:
-                return "blocked by power restrictions";
-            case NTWK_ALLOWED_NON_METERED:
-                return "allowed on unmetered network";
-            case NTWK_BLOCKED_DENYLIST:
-                return "denylisted on metered network";
-            case NTWK_ALLOWED_ALLOWLIST:
-                return "allowlisted on metered network";
-            case NTWK_ALLOWED_TMP_ALLOWLIST:
-                return "temporary allowlisted on metered network";
-            case NTWK_BLOCKED_BG_RESTRICT:
-                return "blocked when background is restricted";
-            case NTWK_ALLOWED_DEFAULT:
-                return "allowed by default";
-            case NTWK_BLOCKED_RESTRICTED_MODE:
-                return "blocked by restricted networking mode";
-            default:
-                return String.valueOf(reason);
-        }
-    }
-
     private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
         return "Policy for " + uid + " changed from "
                 + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
@@ -402,14 +371,17 @@
             data.timeStamp = System.currentTimeMillis();
         }
 
-        public void networkBlocked(int uid, int reason) {
+        public void networkBlocked(int uid, int blockedReasons, int allowedReasons,
+                int effectiveBlockedReasons) {
             final Data data = getNextSlot();
             if (data == null) return;
 
             data.reset();
             data.type = EVENT_NETWORK_BLOCKED;
             data.ifield1 = uid;
-            data.ifield2 = reason;
+            data.ifield2 = blockedReasons;
+            data.ifield3 = allowedReasons;
+            data.ifield4 = effectiveBlockedReasons;
             data.timeStamp = System.currentTimeMillis();
         }
 
@@ -554,7 +526,8 @@
                 case EVENT_TYPE_GENERIC:
                     return data.sfield1;
                 case EVENT_NETWORK_BLOCKED:
-                    return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+                    return data.ifield1 + "-" + UidBlockedState.toString(
+                            data.ifield2, data.ifield3, data.ifield4);
                 case EVENT_UID_STATE_CHANGED:
                     return data.ifield1 + ":" + ProcessList.makeProcStateString(data.ifield2)
                             + ":" + ActivityManager.getCapabilitiesSummary(data.ifield3)
@@ -593,17 +566,18 @@
         }
     }
 
-    public final static class Data {
-        int type;
-        long timeStamp;
+    private static final class Data {
+        public int type;
+        public long timeStamp;
 
-        int ifield1;
-        int ifield2;
-        int ifield3;
-        long lfield1;
-        boolean bfield1;
-        boolean bfield2;
-        String sfield1;
+        public int ifield1;
+        public int ifield2;
+        public int ifield3;
+        public int ifield4;
+        public long lfield1;
+        public boolean bfield1;
+        public boolean bfield2;
+        public String sfield1;
 
         public void reset(){
             sfield1 = null;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9e677e0..64f72c5 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -79,14 +79,10 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_RESTRICTED_MODE_NETWORKS;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -135,15 +131,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_RESTRICTED_MODE;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -516,8 +503,6 @@
 
     /** Defined UID policies. */
     @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
-    /** Currently derived rules for each UID. */
-    @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidRules = new SparseIntArray();
 
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
@@ -596,6 +581,10 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseArray<UidBlockedState> mUidBlockedState = new SparseArray<>();
 
+    /** Objects used temporarily while computing the new blocked state for each uid. */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
+
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -3805,7 +3794,7 @@
 
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
-                collectKeys(mUidRules, knownUids);
+                collectKeys(mUidBlockedState, knownUids);
 
                 fout.println("Status for all known UIDs:");
                 fout.increaseIndent();
@@ -3823,23 +3812,13 @@
                         fout.print(uidState.toString());
                     }
 
-                    final int uidRules = mUidRules.get(uid, RULE_NONE);
-                    fout.print(" rules=");
-                    fout.print(uidRulesToString(uidRules));
-                    fout.println();
-                }
-                fout.decreaseIndent();
-
-                fout.println("Status for just UIDs with rules:");
-                fout.increaseIndent();
-                size = mUidRules.size();
-                for (int i = 0; i < size; i++) {
-                    final int uid = mUidRules.keyAt(i);
-                    fout.print("UID=");
-                    fout.print(uid);
-                    final int uidRules = mUidRules.get(uid, RULE_NONE);
-                    fout.print(" rules=");
-                    fout.print(uidRulesToString(uidRules));
+                    final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+                    if (uidBlockedState == null) {
+                        fout.print(" blocked_state={null}");
+                    } else {
+                        fout.print(" blocked_state=");
+                        fout.print(uidBlockedState.toString());
+                    }
                     fout.println();
                 }
                 fout.decreaseIndent();
@@ -3990,22 +3969,17 @@
     void updateRestrictedModeAllowlistUL() {
         mUidFirewallRestrictedModeRules.clear();
         forEachUid("updateRestrictedModeAllowlist", uid -> {
-            final int oldUidRule = mUidRules.get(uid);
-            final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
-            final boolean hasUidRuleChanged = oldUidRule != newUidRule;
-            final int newFirewallRule = getRestrictedModeFirewallRule(newUidRule);
+            synchronized (mUidRulesFirstLock) {
+                final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(
+                        uid);
+                final int newFirewallRule = getRestrictedModeFirewallRule(uidBlockedState);
 
-            // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
-            // non-default rules.
-            if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
-                mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
+                // non-default rules.
+                if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
+                    mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                }
             }
-
-            if (hasUidRuleChanged) {
-                mUidRules.put(uid, newUidRule);
-                mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
-            }
-            updateBlockedReasonsForRestrictedModeUL(uid);
         });
         if (mRestrictedNetworkingMode) {
             // firewall rules only need to be set when this mode is being enabled.
@@ -4018,15 +3992,7 @@
     @VisibleForTesting
     @GuardedBy("mUidRulesFirstLock")
     void updateRestrictedModeForUidUL(int uid) {
-        final int oldUidRule = mUidRules.get(uid);
-        final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
-        final boolean hasUidRuleChanged = oldUidRule != newUidRule;
-
-        if (hasUidRuleChanged) {
-            mUidRules.put(uid, newUidRule);
-            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
-        }
-        updateBlockedReasonsForRestrictedModeUL(uid);
+        final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(uid);
 
         // if restricted networking mode is on, and the app has an access exemption, the uid rule
         // will not change, but the firewall rule will have to be updated.
@@ -4034,16 +4000,14 @@
             // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
             // In this case, default firewall rules can also be added.
             setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
-                    getRestrictedModeFirewallRule(newUidRule));
+                    getRestrictedModeFirewallRule(uidBlockedState));
         }
     }
 
-    private void updateBlockedReasonsForRestrictedModeUL(int uid) {
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
+    @GuardedBy("mUidRulesFirstLock")
+    private UidBlockedState updateBlockedReasonsForRestrictedModeUL(int uid) {
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
         final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
         if (mRestrictedNetworkingMode) {
             uidBlockedState.blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
@@ -4057,23 +4021,16 @@
         }
         uidBlockedState.updateEffectiveBlockedReasons();
         if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
-            mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
-                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
-                    .sendToTarget();
+            postBlockedReasonsChangedMsg(uid,
+                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
         }
+        return uidBlockedState;
     }
 
-    private int getNewRestrictedModeUidRule(int uid, int oldUidRule) {
-        int newRule = oldUidRule;
-        newRule &= ~MASK_RESTRICTED_MODE_NETWORKS;
-        if (mRestrictedNetworkingMode && !hasRestrictedModeAccess(uid)) {
-            newRule |= RULE_REJECT_RESTRICTED_MODE;
-        }
-        return newRule;
-    }
-
-    private static int getRestrictedModeFirewallRule(int uidRule) {
-        if ((uidRule & RULE_REJECT_RESTRICTED_MODE) != 0) {
+    private static int getRestrictedModeFirewallRule(UidBlockedState uidBlockedState) {
+        if ((uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
             // rejected in restricted mode, this is the default behavior.
             return FIREWALL_RULE_DEFAULT;
         } else {
@@ -4281,16 +4238,12 @@
             if (!isUidValidForDenylistRulesUL(uid)) {
                 continue;
             }
-            int oldRules = mUidRules.get(uid);
-            if (enableChain) {
-                // Chain wasn't enabled before and the other power-related
-                // chains are allowlists, so we can clear the
-                // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
-                // the effective rules result in blocking network access.
-                oldRules &= MASK_METERED_NETWORKS;
-            } else {
-                // Skip if it had no restrictions to begin with
-                if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
+            final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                    mUidBlockedState, uid);
+            if (!enableChain && (uidBlockedState.blockedReasons & ~BLOCKED_METERED_REASON_MASK)
+                    == BLOCKED_REASON_NONE) {
+                // Chain isn't enabled and the uid had no restrictions to begin with.
+                continue;
             }
             final boolean isUidIdle = !paroled && isUidIdle(uid);
             if (isUidIdle && !mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid))
@@ -4300,13 +4253,7 @@
             } else {
                 mUidFirewallStandbyRules.put(uid, FIREWALL_RULE_DEFAULT);
             }
-            final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules,
-                    isUidIdle);
-            if (newUidRules == RULE_NONE) {
-                mUidRules.delete(uid);
-            } else {
-                mUidRules.put(uid, newUidRules);
-            }
+            updateRulesForPowerRestrictionsUL(uid, isUidIdle);
         }
         setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, blockedUids,
                 enableChain ? CHAIN_TOGGLE_ENABLE : CHAIN_TOGGLE_DISABLE);
@@ -4524,6 +4471,7 @@
             mInternetPermissionMap.put(uid, hasPermission);
             return hasPermission;
         } catch (RemoteException e) {
+            // ignored; service lives in system_server
         }
         return true;
     }
@@ -4534,7 +4482,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private void onUidDeletedUL(int uid) {
         // First cleanup in-memory state synchronously...
-        mUidRules.delete(uid);
+        mUidBlockedState.delete(uid);
         mUidPolicy.delete(uid);
         mUidFirewallStandbyRules.delete(uid);
         mUidFirewallDozableRules.delete(uid);
@@ -4620,7 +4568,7 @@
      * permission, since there is no need to change the {@code iptables} rule if the app does not
      * have permission to use the internet.
      *
-     * <p>The {@link #mUidRules} map is used to define the transtion of states of an UID.
+     * <p>The {@link #mUidBlockedState} map is used to define the transition of states of an UID.
      *
      */
     private void updateRulesForDataUsageRestrictionsUL(int uid) {
@@ -4635,6 +4583,7 @@
         }
     }
 
+    @GuardedBy("mUidRulesFirstLock")
     private void updateRulesForDataUsageRestrictionsULInner(int uid) {
         if (!isUidValidForAllowlistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
@@ -4642,38 +4591,17 @@
         }
 
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
-        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
         final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
         final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
+        final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+                mTmpUidBlockedState, uid);
+        previousUidBlockedState.copyFrom(uidBlockedState);
 
         final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
         final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
 
-        // copy oldUidRules and clear out METERED_NETWORKS rules.
-        int newUidRules = oldUidRules & (~MASK_METERED_NETWORKS);
-
-        // First step: define the new rule based on user restrictions and foreground state.
-        if (isRestrictedByAdmin) {
-            newUidRules |= RULE_REJECT_METERED;
-        } else if (isForeground) {
-            if (isDenied || (mRestrictBackground && !isAllowed)) {
-                newUidRules |= RULE_TEMPORARY_ALLOW_METERED;
-            } else if (isAllowed) {
-                newUidRules |= RULE_ALLOW_METERED;
-            }
-        } else {
-            if (isDenied) {
-                newUidRules |= RULE_REJECT_METERED;
-            } else if (mRestrictBackground && isAllowed) {
-                newUidRules |= RULE_ALLOW_METERED;
-            }
-        }
-
         int newBlockedReasons = BLOCKED_REASON_NONE;
         int newAllowedReasons = ALLOWED_REASON_NONE;
         newBlockedReasons |= (isRestrictedByAdmin ? BLOCKED_METERED_REASON_ADMIN_DISABLED : 0);
@@ -4684,16 +4612,48 @@
         newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
         newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
 
+        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+                & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+                & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+        uidBlockedState.updateEffectiveBlockedReasons();
+        final int oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
+        final int newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+        if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
+            postBlockedReasonsChangedMsg(uid,
+                    newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+        }
+
+        // Note that the conditionals below are for avoiding unnecessary calls to netd.
+        // TODO: Measure the performance for doing a no-op call to netd so that we can
+        // remove the conditionals to simplify the logic below. We can also further reduce
+        // some calls to netd if they turn out to be costly.
+        final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+                | BLOCKED_METERED_REASON_USER_RESTRICTED;
+        if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
+                || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
+            setMeteredNetworkDenylist(uid,
+                    (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
+        }
+        final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
+                | ALLOWED_METERED_REASON_USER_EXEMPTED;
+        final int oldAllowedReasons = previousUidBlockedState.allowedReasons;
+        if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
+                || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
+            setMeteredNetworkAllowlist(uid,
+                    (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+        }
+
         if (LOGV) {
             Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
                     + ": isForeground=" +isForeground
                     + ", isDenied=" + isDenied
                     + ", isAllowed=" + isAllowed
                     + ", isRestrictedByAdmin=" + isRestrictedByAdmin
-                    + ", oldRule=" + uidRulesToString(oldUidRules & MASK_METERED_NETWORKS)
-                    + ", newRule=" + uidRulesToString(newUidRules & MASK_METERED_NETWORKS)
-                    + ", newUidRules=" + uidRulesToString(newUidRules)
-                    + ", oldUidRules=" + uidRulesToString(oldUidRules)
+                    + ", oldBlockedState=" + previousUidBlockedState.toString()
+                    + ", newBlockedState="
                     + ", oldBlockedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
                     uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK)
                     + ", oldBlockedMeteredEffectiveReasons="
@@ -4702,84 +4662,11 @@
                     + ", oldAllowedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
                     uidBlockedState.allowedReasons & BLOCKED_METERED_REASON_MASK));
         }
-
-        if (newUidRules == RULE_NONE) {
-            mUidRules.delete(uid);
-        } else {
-            mUidRules.put(uid, newUidRules);
-        }
-
-        // Second step: apply bw changes based on change of state.
-        if (newUidRules != oldUidRules) {
-            if (hasRule(newUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Temporarily allow foreground app, removing from denylist if necessary
-                // (since bw_penalty_box prevails over bw_happy_box).
-
-                setMeteredNetworkAllowlist(uid, true);
-                // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
-                // but ideally it should be just:
-                //    setMeteredNetworkDenylist(uid, isDenied);
-                if (isDenied) {
-                    setMeteredNetworkDenylist(uid, false);
-                }
-            } else if (hasRule(oldUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Remove temporary exemption from app that is not on foreground anymore.
-
-                // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
-                // but ideally they should be just:
-                //    setMeteredNetworkAllowlist(uid, isAllowed);
-                //    setMeteredNetworkDenylist(uid, isDenied);
-                if (!isAllowed) {
-                    setMeteredNetworkAllowlist(uid, false);
-                }
-                if (isDenied || isRestrictedByAdmin) {
-                    setMeteredNetworkDenylist(uid, true);
-                }
-            } else if (hasRule(newUidRules, RULE_REJECT_METERED)
-                    || hasRule(oldUidRules, RULE_REJECT_METERED)) {
-                // Flip state because app was explicitly added or removed to denylist.
-                setMeteredNetworkDenylist(uid, (isDenied || isRestrictedByAdmin));
-                if (hasRule(oldUidRules, RULE_REJECT_METERED) && isAllowed) {
-                    // Since denial prevails over allowance, we need to handle the special case
-                    // where app is allowed and denied at the same time (although such
-                    // scenario should be blocked by the UI), then it is removed from the denylist.
-                    setMeteredNetworkAllowlist(uid, isAllowed);
-                }
-            } else if (hasRule(newUidRules, RULE_ALLOW_METERED)
-                    || hasRule(oldUidRules, RULE_ALLOW_METERED)) {
-                // Flip state because app was explicitly added or removed to allowlist.
-                setMeteredNetworkAllowlist(uid, isAllowed);
-            } else {
-                // All scenarios should have been covered above.
-                Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
-                        + ": foreground=" + isForeground
-                        + ", allowlisted=" + isAllowed
-                        + ", denylisted=" + isDenied
-                        + ", isRestrictedByAdmin=" + isRestrictedByAdmin
-                        + ", newRule=" + uidRulesToString(newUidRules)
-                        + ", oldRule=" + uidRulesToString(oldUidRules));
-            }
-
-            // 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();
-        }
     }
 
     /**
-     * Updates the power-related part of the {@link #mUidRules} for a given map, and notify external
-     * listeners in case of change.
+     * Updates the power-related part of the {@link #mUidBlockedState} for a given map, and
+     * notify external listeners in case of change.
      * <p>
      * There are 3 power-related rules that affects whether an app has background access on
      * non-metered networks, and when the condition applies and the UID is not allowed for power
@@ -4790,23 +4677,15 @@
      * <li>Battery Saver Mode is on: {@code fw_powersave} firewall chain.
      * </ul>
      * <p>
-     * This method updates the power-related part of the {@link #mUidRules} for a given uid based on
-     * these modes, the UID process state (foreground or not), and the UID allowlist state.
+     * This method updates the power-related part of the {@link #mUidBlockedState} for a given
+     * uid based on these modes, the UID process state (foreground or not), and the UID
+     * allowlist state.
      * <p>
      * <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
      */
     @GuardedBy("mUidRulesFirstLock")
     private void updateRulesForPowerRestrictionsUL(int uid) {
-        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
-
-        final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules,
-                isUidIdle(uid));
-
-        if (newUidRules == RULE_NONE) {
-            mUidRules.delete(uid);
-        } else {
-            mUidRules.put(uid, newUidRules);
-        }
+        updateRulesForPowerRestrictionsUL(uid, isUidIdle(uid));
     }
 
     /**
@@ -4815,56 +4694,37 @@
      * @param uid the uid of the app to update rules for
      * @param oldUidRules the current rules for the uid, in order to determine if there's a change
      * @param isUidIdle whether uid is idle or not
-     *
-     * @return the new computed rules for the uid
      */
     @GuardedBy("mUidRulesFirstLock")
-    private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean isUidIdle) {
+    private void updateRulesForPowerRestrictionsUL(int uid, boolean isUidIdle) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
-                    "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+                    "updateRulesForPowerRestrictionsUL: " + uid + "/"
                             + (isUidIdle ? "I" : "-"));
         }
         try {
-            return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, isUidIdle);
+            updateRulesForPowerRestrictionsULInner(uid, isUidIdle);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules,
-            boolean isUidIdle) {
+    private void updateRulesForPowerRestrictionsULInner(int uid, boolean isUidIdle) {
         if (!isUidValidForDenylistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
-            return RULE_NONE;
+            return;
         }
 
-        final boolean restrictMode = isUidIdle || mRestrictPower || mDeviceIdleMode;
         final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
 
         final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
 
-        // Copy existing uid rules and clear ALL_NETWORK rules.
-        int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS);
-
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
-
-        // First step: define the new rule based on user restrictions and foreground state.
-
-        // NOTE: if statements below could be inlined, but it's easier to understand the logic
-        // by considering the foreground and non-foreground states.
-        if (isForeground) {
-            if (restrictMode) {
-                newUidRules |= RULE_ALLOW_ALL;
-            }
-        } else if (restrictMode) {
-            newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
-        }
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
+        final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+                mTmpUidBlockedState, uid);
+        previousUidBlockedState.copyFrom(uidBlockedState);
 
         int newBlockedReasons = BLOCKED_REASON_NONE;
         int newAllowedReasons = ALLOWED_REASON_NONE;
@@ -4880,6 +4740,20 @@
         newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
                 ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
 
+        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+                & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+                & ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+        uidBlockedState.updateEffectiveBlockedReasons();
+        if (previousUidBlockedState.effectiveBlockedReasons
+                != uidBlockedState.effectiveBlockedReasons) {
+            postBlockedReasonsChangedMsg(uid,
+                    uidBlockedState.effectiveBlockedReasons,
+                    previousUidBlockedState.effectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+        }
+
         if (LOGV) {
             Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
                     + ", isIdle: " + isUidIdle
@@ -4887,43 +4761,9 @@
                     + ", mDeviceIdleMode: " + mDeviceIdleMode
                     + ", isForeground=" + isForeground
                     + ", isWhitelisted=" + isWhitelisted
-                    + ", oldRule=" + uidRulesToString(oldUidRules & MASK_ALL_NETWORKS)
-                    + ", newRule=" + uidRulesToString(newUidRules & MASK_ALL_NETWORKS)
-                    + ", newUidRules=" + uidRulesToString(newUidRules)
-                    + ", oldUidRules=" + uidRulesToString(oldUidRules));
+                    + ", oldUidBlockedState=" + previousUidBlockedState.toString()
+                    + ", newUidBlockedState=" + uidBlockedState.toString());
         }
-
-        // Second step: notify listeners if state changed.
-        if (newUidRules != oldUidRules) {
-            if ((newUidRules & MASK_ALL_NETWORKS) == RULE_NONE || hasRule(newUidRules,
-                    RULE_ALLOW_ALL)) {
-                if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
-            } else if (hasRule(newUidRules, RULE_REJECT_ALL)) {
-                if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
-            } else {
-                // All scenarios should have been covered above
-                Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
-                        + ": foreground=" + isForeground
-                        + ", whitelisted=" + isWhitelisted
-                        + ", newRule=" + uidRulesToString(newUidRules)
-                        + ", oldRule=" + uidRulesToString(oldUidRules));
-            }
-            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();
-        }
-
-        return newUidRules;
     }
 
     private class NetPolicyAppIdleStateChangeListener extends AppIdleStateChangeListener {
@@ -4951,10 +4791,23 @@
         }
     }
 
+    private void postBlockedReasonsChangedMsg(int uid, int newEffectiveBlockedReasons,
+            int oldEffectiveBlockedReasons) {
+        mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+                newEffectiveBlockedReasons, oldEffectiveBlockedReasons)
+                .sendToTarget();
+    }
+
+    private void postUidRulesChangedMsg(int uid, int uidRules) {
+        mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules)
+                .sendToTarget();
+    }
+
     private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
         try {
             listener.onUidRulesChanged(uid, uidRules);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4963,6 +4816,7 @@
         try {
             listener.onMeteredIfacesChanged(meteredIfaces);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4971,6 +4825,7 @@
         try {
             listener.onRestrictBackgroundChanged(restrictBackground);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4979,6 +4834,7 @@
         try {
             listener.onUidPoliciesChanged(uid, uidPolicies);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4987,6 +4843,7 @@
         try {
             listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4995,6 +4852,7 @@
         try {
             listener.onSubscriptionPlansChanged(subId, plans);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5003,6 +4861,7 @@
         try {
             listener.onBlockedReasonChanged(uid, oldBlockedReasons, newBlockedReasons);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5013,6 +4872,10 @@
                 case MSG_RULES_CHANGED: {
                     final int uid = msg.arg1;
                     final int uidRules = msg.arg2;
+                    if (LOGV) {
+                        Slog.v(TAG, "Dispatching rules=" + uidRulesToString(uidRules)
+                                + " for uid=" + uid);
+                    }
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
@@ -5581,7 +5444,7 @@
         }
     }
 
-    private static void collectKeys(SparseArray<UidState> source, SparseBooleanArray target) {
+    private static <T> void collectKeys(SparseArray<T> source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
             target.put(source.keyAt(i), true);
@@ -5629,90 +5492,38 @@
         final long startTime = mStatLogger.getTime();
 
         mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
-        final int uidRules;
-        final boolean isBackgroundRestricted;
+        int blockedReasons;
         synchronized (mUidRulesFirstLock) {
-            uidRules = mUidRules.get(uid, RULE_NONE);
-            isBackgroundRestricted = mRestrictBackground;
+            final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+            blockedReasons = uidBlockedState == null
+                    ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+            if (!isNetworkMetered) {
+                blockedReasons &= ~BLOCKED_METERED_REASON_MASK;
+            }
+            mLogger.networkBlocked(uid, uidBlockedState);
         }
-        final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
-                isBackgroundRestricted, mLogger);
 
         mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime);
 
-        return ret;
+        return blockedReasons != BLOCKED_REASON_NONE;
     }
 
     @Override
     public boolean isUidRestrictedOnMeteredNetworks(int uid) {
         mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
-        final int uidRules;
-        final boolean isBackgroundRestricted;
         synchronized (mUidRulesFirstLock) {
-            uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-            isBackgroundRestricted = mRestrictBackground;
+            final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+            int blockedReasons = uidBlockedState == null
+                    ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+            blockedReasons &= BLOCKED_METERED_REASON_MASK;
+            return blockedReasons != BLOCKED_REASON_NONE;
         }
-        // TODO(b/177490332): The logic here might not be correct because it doesn't consider
-        //  RULE_REJECT_METERED condition. And it could be replaced by
-        //  isUidNetworkingBlockedInternal().
-        return isBackgroundRestricted
-                && !hasRule(uidRules, RULE_ALLOW_METERED)
-                && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED);
     }
 
     private static boolean isSystem(int uid) {
         return uid < Process.FIRST_APPLICATION_UID;
     }
 
-    static boolean isUidNetworkingBlockedInternal(int uid, int uidRules, boolean isNetworkMetered,
-            boolean isBackgroundRestricted, @Nullable NetworkPolicyLogger logger) {
-        final int reason;
-        // Networks are never blocked for system components
-        if (isSystem(uid)) {
-            reason = NTWK_ALLOWED_SYSTEM;
-        } else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) {
-            reason = NTWK_BLOCKED_RESTRICTED_MODE;
-        } else if (hasRule(uidRules, RULE_REJECT_ALL)) {
-            reason = NTWK_BLOCKED_POWER;
-        } else if (!isNetworkMetered) {
-            reason = NTWK_ALLOWED_NON_METERED;
-        } else if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            reason = NTWK_BLOCKED_DENYLIST;
-        } else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_ALLOWLIST;
-        } else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_TMP_ALLOWLIST;
-        } else if (isBackgroundRestricted) {
-            reason = NTWK_BLOCKED_BG_RESTRICT;
-        } else {
-            reason = NTWK_ALLOWED_DEFAULT;
-        }
-
-        final boolean blocked;
-        switch(reason) {
-            case NTWK_ALLOWED_DEFAULT:
-            case NTWK_ALLOWED_NON_METERED:
-            case NTWK_ALLOWED_TMP_ALLOWLIST:
-            case NTWK_ALLOWED_ALLOWLIST:
-            case NTWK_ALLOWED_SYSTEM:
-                blocked = false;
-                break;
-            case NTWK_BLOCKED_RESTRICTED_MODE:
-            case NTWK_BLOCKED_POWER:
-            case NTWK_BLOCKED_DENYLIST:
-            case NTWK_BLOCKED_BG_RESTRICT:
-                blocked = true;
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-        if (logger != null) {
-            logger.networkBlocked(uid, reason);
-        }
-
-        return blocked;
-    }
-
     private class NetworkPolicyManagerInternalImpl extends NetworkPolicyManagerInternal {
 
         @Override
@@ -5921,6 +5732,16 @@
         return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue;
     }
 
+    private static UidBlockedState getOrCreateUidBlockedStateForUid(
+            SparseArray<UidBlockedState> uidBlockedStates, int uid) {
+        UidBlockedState uidBlockedState = uidBlockedStates.get(uid);
+        if (uidBlockedState == null) {
+            uidBlockedState = new UidBlockedState();
+            uidBlockedStates.put(uid, uidBlockedState);
+        }
+        return uidBlockedState;
+    }
+
     @VisibleForTesting
     static final class UidBlockedState {
         public int blockedReasons;
@@ -5984,9 +5805,180 @@
             }
             return effectiveBlockedReasons;
         }
+
+        @Override
+        public String toString() {
+            return toString(blockedReasons, allowedReasons, effectiveBlockedReasons);
+        }
+
+        public static String toString(int blockedReasons, int allowedReasons,
+                int effectiveBlockedReasons) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            sb.append("blocked=").append(blockedReasonsToString(blockedReasons)).append(",");
+            sb.append("allowed=").append(allowedReasonsToString(allowedReasons)).append(",");
+            sb.append("effective=").append(blockedReasonsToString(effectiveBlockedReasons));
+            sb.append("}");
+            return sb.toString();
+        }
+
+        private static final int[] BLOCKED_REASONS = {
+                BLOCKED_REASON_BATTERY_SAVER,
+                BLOCKED_REASON_DOZE,
+                BLOCKED_REASON_APP_STANDBY,
+                BLOCKED_REASON_RESTRICTED_MODE,
+                BLOCKED_METERED_REASON_DATA_SAVER,
+                BLOCKED_METERED_REASON_USER_RESTRICTED,
+                BLOCKED_METERED_REASON_ADMIN_DISABLED,
+        };
+
+        private static final int[] ALLOWED_REASONS = {
+                ALLOWED_REASON_SYSTEM,
+                ALLOWED_REASON_FOREGROUND,
+                ALLOWED_REASON_POWER_SAVE_ALLOWLIST,
+                ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
+                ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
+                ALLOWED_METERED_REASON_USER_EXEMPTED,
+                ALLOWED_METERED_REASON_SYSTEM,
+                ALLOWED_METERED_REASON_FOREGROUND,
+        };
+
+        private static String blockedReasonToString(int blockedReason) {
+            switch (blockedReason) {
+                case BLOCKED_REASON_NONE:
+                    return "NONE";
+                case BLOCKED_REASON_BATTERY_SAVER:
+                    return "BATTERY_SAVER";
+                case BLOCKED_REASON_DOZE:
+                    return "DOZE";
+                case BLOCKED_REASON_APP_STANDBY:
+                    return "APP_STANDBY";
+                case BLOCKED_REASON_RESTRICTED_MODE:
+                    return "RESTRICTED_MODE";
+                case BLOCKED_METERED_REASON_DATA_SAVER:
+                    return "DATA_SAVER";
+                case BLOCKED_METERED_REASON_USER_RESTRICTED:
+                    return "METERED_USER_RESTRICTED";
+                case BLOCKED_METERED_REASON_ADMIN_DISABLED:
+                    return "METERED_ADMIN_DISABLED";
+                default:
+                    Slog.wtfStack(TAG, "Unknown blockedReason: " + blockedReason);
+                    return String.valueOf(blockedReason);
+            }
+        }
+
+        private static String allowedReasonToString(int allowedReason) {
+            switch (allowedReason) {
+                case ALLOWED_REASON_NONE:
+                    return "NONE";
+                case ALLOWED_REASON_SYSTEM:
+                    return "SYSTEM";
+                case ALLOWED_REASON_FOREGROUND:
+                    return "FOREGROUND";
+                case ALLOWED_REASON_POWER_SAVE_ALLOWLIST:
+                    return "POWER_SAVE_ALLOWLIST";
+                case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST:
+                    return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST";
+                case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS:
+                    return "RESTRICTED_MODE_PERMISSIONS";
+                case ALLOWED_METERED_REASON_USER_EXEMPTED:
+                    return "METERED_USER_EXEMPTED";
+                case ALLOWED_METERED_REASON_SYSTEM:
+                    return "METERED_SYSTEM";
+                case ALLOWED_METERED_REASON_FOREGROUND:
+                    return "METERED_FOREGROUND";
+                default:
+                    Slog.wtfStack(TAG, "Unknown allowedReason: " + allowedReason);
+                    return String.valueOf(allowedReason);
+            }
+        }
+
+        public static String blockedReasonsToString(int blockedReasons) {
+            if (blockedReasons == BLOCKED_REASON_NONE) {
+                return blockedReasonToString(BLOCKED_REASON_NONE);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int reason : BLOCKED_REASONS) {
+                if ((blockedReasons & reason) != 0) {
+                    sb.append(sb.length() == 0 ? "" : "|");
+                    sb.append(blockedReasonToString(reason));
+                    blockedReasons &= ~reason;
+                }
+            }
+            if (blockedReasons != 0) {
+                sb.append(sb.length() == 0 ? "" : "|");
+                sb.append(String.valueOf(blockedReasons));
+                Slog.wtfStack(TAG, "Unknown blockedReasons: " + blockedReasons);
+            }
+            return sb.toString();
+        }
+
+        public static String allowedReasonsToString(int allowedReasons) {
+            if (allowedReasons == ALLOWED_REASON_NONE) {
+                return allowedReasonToString(ALLOWED_REASON_NONE);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int reason : ALLOWED_REASONS) {
+                if ((allowedReasons & reason) != 0) {
+                    sb.append(sb.length() == 0 ? "" : "|");
+                    sb.append(allowedReasonToString(reason));
+                    allowedReasons &= ~reason;
+                }
+            }
+            if (allowedReasons != 0) {
+                sb.append(sb.length() == 0 ? "" : "|");
+                sb.append(String.valueOf(allowedReasons));
+                Slog.wtfStack(TAG, "Unknown allowedReasons: " + allowedReasons);
+            }
+            return sb.toString();
+        }
+
+        public void copyFrom(UidBlockedState uidBlockedState) {
+            blockedReasons = uidBlockedState.blockedReasons;
+            allowedReasons = uidBlockedState.allowedReasons;
+            effectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+        }
+
+        public int deriveUidRules() {
+            int uidRule = RULE_NONE;
+            if ((effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
+                uidRule |= RULE_REJECT_RESTRICTED_MODE;
+            }
+
+            int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
+                    | BLOCKED_REASON_DOZE
+                    | BLOCKED_REASON_BATTERY_SAVER;
+            if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
+                uidRule |= RULE_REJECT_ALL;
+            } else if ((blockedReasons & powerBlockedReasons) != 0) {
+                uidRule |= RULE_ALLOW_ALL;
+            }
+
+            // UidRule doesn't include RestrictBackground (DataSaver) state, so not including in
+            // metered blocked reasons below.
+            int meteredBlockedReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+                    | BLOCKED_METERED_REASON_USER_RESTRICTED;
+            if ((effectiveBlockedReasons & meteredBlockedReasons) != 0) {
+                uidRule |= RULE_REJECT_METERED;
+            } else if ((blockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) != 0
+                    && (allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+                uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+            } else if ((blockedReasons & BLOCKED_METERED_REASON_DATA_SAVER) != 0) {
+                if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
+                    uidRule |= RULE_ALLOW_ALL;
+                } else if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+                    uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+                }
+            }
+            if (LOGV) {
+                Slog.v(TAG, "uidBlockedState=" + this.toString()
+                        + " -> uidRule=" + uidRulesToString(uidRule));
+            }
+            return uidRule;
+        }
     }
 
-    private class NotificationId {
+    private static class NotificationId {
         private final String mTag;
         private final int mId;
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2b39234..b880a61 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -259,6 +260,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.InstanceId;
@@ -559,6 +562,7 @@
     ArrayList<String> mLights = new ArrayList<>();
 
     private AppOpsManager mAppOps;
+    private IAppOpsService mAppOpsService;
     private UsageStatsManagerInternal mAppUsageStats;
     private DevicePolicyManagerInternal mDpm;
     private StatsManager mStatsManager;
@@ -1667,6 +1671,18 @@
         }
     };
 
+    @VisibleForTesting
+    final IAppOpsCallback mAppOpsCallback = new IAppOpsCallback.Stub() {
+        @Override public void opChanged(int op, int uid, String packageName) {
+            if (mEnableAppSettingMigration) {
+                int opValue = mAppOps.checkOpNoThrow(
+                        AppOpsManager.OP_POST_NOTIFICATION, uid, packageName);
+                boolean blocked = op != MODE_ALLOWED;
+                sendAppBlockStateChangedBroadcast(packageName, uid, blocked);
+            }
+        }
+    };
+
     private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -2176,7 +2192,8 @@
             ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
             ActivityTaskManagerInternal atm, UsageStatsManagerInternal appUsageStats,
             DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
-            UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
+            UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, IAppOpsService iAppOps,
+            UserManager userManager,
             NotificationHistoryManager historyManager, StatsManager statsManager,
             TelephonyManager telephonyManager, ActivityManagerInternal ami,
             MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) {
@@ -2196,6 +2213,13 @@
         mPackageManager = packageManager;
         mPackageManagerClient = packageManagerClient;
         mAppOps = appOps;
+        mAppOpsService = iAppOps;
+        try {
+            mAppOpsService.startWatchingMode(
+                    AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Could not register OP_POST_NOTIFICATION listener");
+        }
         mAppUsageStats = appUsageStats;
         mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
         mCompanionManager = companionManager;
@@ -2484,7 +2508,8 @@
                 LocalServices.getService(DevicePolicyManagerInternal.class),
                 UriGrantsManager.getService(),
                 LocalServices.getService(UriGrantsManagerInternal.class),
-                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
+                getContext().getSystemService(AppOpsManager.class),
+                IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE)),
                 getContext().getSystemService(UserManager.class),
                 new NotificationHistoryManager(getContext(), handler),
                 mStatsManager = (StatsManager) getContext().getSystemService(
@@ -2722,6 +2747,19 @@
         });
     }
 
+    private void sendAppBlockStateChangedBroadcast(String pkg, int uid, boolean blocked) {
+        try {
+            getContext().sendBroadcastAsUser(
+                    new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
+                            .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, blocked)
+                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                            .setPackage(pkg),
+                    UserHandle.of(UserHandle.getUserId(uid)), null);
+        } catch (SecurityException e) {
+            Slog.w(TAG, "Can't notify app about app block change", e);
+        }
+    }
+
     @Override
     public void onUserStopping(@NonNull TargetUser user) {
         mHandler.post(() -> {
@@ -3429,7 +3467,8 @@
                 if (wasEnabled == enabled) {
                     return;
                 }
-                mPermissionHelper.setNotificationPermission(pkg, uid, enabled, true);
+                mPermissionHelper.setNotificationPermission(
+                        pkg, UserHandle.getUserId(uid), enabled, true);
             } else {
                 synchronized (mNotificationLock) {
                     boolean wasEnabled = mPreferencesHelper.getImportance(pkg, uid)
@@ -3441,6 +3480,12 @@
                 }
 
                 mPreferencesHelper.setEnabled(pkg, uid, enabled);
+                // TODO (b/194833441): this is being ignored by app ops now that the permission
+                // exists
+                mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+                        enabled ? MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+
+                sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
             }
             mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
                     .setType(MetricsEvent.TYPE_ACTION)
@@ -3452,19 +3497,6 @@
                         UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
             }
 
-            mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
-                    enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-            try {
-                getContext().sendBroadcastAsUser(
-                        new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
-                                .putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
-                                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                                .setPackage(pkg),
-                        UserHandle.of(UserHandle.getUserId(uid)), null);
-            } catch (SecurityException e) {
-                Slog.w(TAG, "Can't notify app about app block change", e);
-            }
-
             handleSavePolicyFile();
         }
 
@@ -4050,12 +4082,6 @@
         }
 
         @Override
-        public int getBlockedAppCount(int userId) {
-            checkCallerIsSystem();
-            return mPreferencesHelper.getBlockedAppCount(userId);
-        }
-
-        @Override
         public int getAppsBypassingDndCount(int userId) {
             checkCallerIsSystem();
             return mPreferencesHelper.getAppsBypassingDndCount(userId);
@@ -4198,7 +4224,7 @@
             // noteOp will check to make sure the callingPkg matches the uid
             if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
                     callingAttributionTag, null)
-                    == AppOpsManager.MODE_ALLOWED) {
+                    == MODE_ALLOWED) {
                 synchronized (mNotificationLock) {
                     tmp = new StatusBarNotification[mNotificationList.size()];
                     final int N = mNotificationList.size();
@@ -4314,7 +4340,7 @@
             // noteOp will check to make sure the callingPkg matches the uid
             if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
                     callingAttributionTag, null)
-                    == AppOpsManager.MODE_ALLOWED) {
+                    == MODE_ALLOWED) {
                 synchronized (mArchive) {
                     tmp = mArchive.getArray(count, includeSnoozed);
                 }
@@ -4340,7 +4366,7 @@
             // noteOp will check to make sure the callingPkg matches the uid
             if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
                     callingAttributionTag, null)
-                    == AppOpsManager.MODE_ALLOWED) {
+                    == MODE_ALLOWED) {
                 IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
                 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryReadHistory");
                 try {
@@ -7121,7 +7147,6 @@
                         r.isUpdate = true;
                         final boolean isInterruptive = isVisuallyInterruptive(old, r);
                         r.setTextChanged(isInterruptive);
-                        r.setInterruptive(isInterruptive);
                     }
 
                     mNotificationsByKey.put(n.getKey(), r);
@@ -9469,7 +9494,7 @@
                     record.getSystemGeneratedSmartActions(),
                     record.getSmartReplies(),
                     record.canBubble(),
-                    record.isInterruptive(),
+                    record.isTextChanged(),
                     record.isConversation(),
                     record.getShortcutInfo(),
                     record.getRankingScore() == 0
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 7e94f0d..64abe2e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1157,6 +1157,10 @@
         return mIsInterruptive;
     }
 
+    public boolean isTextChanged() {
+        return mTextChanged;
+    }
+
     /** Returns the time the notification audibly alerted the user. */
     public long getLastAudiblyAlertedMs() {
         return mLastAudiblyAlertedMs;
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 0a34724..5e381ea 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -21,6 +21,7 @@
 import static android.permission.PermissionManager.PERMISSION_GRANTED;
 
 import android.Manifest;
+import android.annotation.UserIdInt;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.ParceledListSlice;
@@ -133,7 +134,7 @@
      * can prevent the user from seeing the in app permission dialog. Must not be called
      * with a lock held.
      */
-    public void setNotificationPermission(String packageName, int userId, boolean grant,
+    public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
             boolean userSet) {
         assertFlag();
         try {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index e8a3a81..6cf7d06 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1621,21 +1621,6 @@
         }
     }
 
-    public int getBlockedAppCount(int userId) {
-        int count = 0;
-        synchronized (mPackagePreferences) {
-            final int N = mPackagePreferences.size();
-            for (int i = 0; i < N; i++) {
-                final PackagePreferences r = mPackagePreferences.valueAt(i);
-                if (userId == UserHandle.getUserId(r.uid)
-                        && r.importance == IMPORTANCE_NONE) {
-                    count++;
-                }
-            }
-        }
-        return count;
-    }
-
     /**
      * Returns the number of apps that have at least one notification channel that can bypass DND
      * for given particular user
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 8387482..850cf1b 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -62,12 +62,21 @@
     private final PackageManagerService mPm;
     private final Installer mInstaller;
     private final ArtManagerService mArtManagerService;
+    private final PackageManagerServiceInjector mInjector;
 
     // TODO(b/198166813): remove PMS dependency
     AppDataHelper(PackageManagerService pm) {
         mPm = pm;
-        mInstaller = mPm.mInjector.getInstaller();
-        mArtManagerService = mPm.mInjector.getArtManagerService();
+        mInjector = mPm.mInjector;
+        mInstaller = mInjector.getInstaller();
+        mArtManagerService = mInjector.getArtManagerService();
+    }
+
+    AppDataHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+        mPm = pm;
+        mInjector = injector;
+        mInstaller = injector.getInstaller();
+        mArtManagerService = injector.getArtManagerService();
     }
 
     /**
@@ -90,8 +99,8 @@
         }
 
         Installer.Batch batch = new Installer.Batch();
-        UserManagerInternal umInternal = mPm.mInjector.getUserManagerInternal();
-        StorageManagerInternal smInternal = mPm.mInjector.getLocalService(
+        UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+        StorageManagerInternal smInternal = mInjector.getLocalService(
                 StorageManagerInternal.class);
         for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) {
             final int flags;
@@ -313,7 +322,7 @@
      */
     @NonNull
     public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
-        final StorageManager storage = mPm.mInjector.getSystemService(StorageManager.class);
+        final StorageManager storage = mInjector.getSystemService(StorageManager.class);
         for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
             final String volumeUuid = vol.getFsUuid();
             synchronized (mPm.mInstallLock) {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
new file mode 100644
index 0000000..d945274
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
@@ -0,0 +1,37 @@
+/*
+ * 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.pm;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+/**
+ * JobService to run background dex optimization. This is a thin wrapper and most logic exits in
+ * {@link BackgroundDexOptService}.
+ */
+public final class BackgroundDexOptJobService extends JobService {
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        return BackgroundDexOptService.getService().onStartJob(this, params);
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return BackgroundDexOptService.getService().onStopJob(this, params);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 49a0a88..44e8e66 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -18,11 +18,11 @@
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
-import android.app.job.JobService;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,63 +30,88 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.os.BatteryManagerInternal;
+import android.os.Binder;
 import android.os.Environment;
 import android.os.IThermalService;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.PinnerService;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.utils.TimingsTraceAndSlog;
 
 import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
 
 /**
- * {@hide}
+ * Controls background dex optimization run as idle job or command line.
  */
-public class BackgroundDexOptService extends JobService {
+public final class BackgroundDexOptService {
     private static final String TAG = "BackgroundDexOptService";
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final int JOB_IDLE_OPTIMIZE = 800;
-    private static final int JOB_POST_BOOT_UPDATE = 801;
+    @VisibleForTesting
+    static final int JOB_IDLE_OPTIMIZE = 800;
+    @VisibleForTesting
+    static final int JOB_POST_BOOT_UPDATE = 801;
 
     private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
 
-    private static ComponentName sDexoptServiceName = new ComponentName(
-            "android",
-            BackgroundDexOptService.class.getName());
+    private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
+
+    private static ComponentName sDexoptServiceName = new ComponentName("android",
+            BackgroundDexOptJobService.class.getName());
 
     // Possible return codes of individual optimization steps.
+    /** Ok status: Optimizations finished, All packages were processed, can continue */
+    private static final int STATUS_OK = 0;
+    /** Optimizations should be aborted. Job scheduler requested it. */
+    private static final int STATUS_ABORT_BY_CANCELLATION = 1;
+    /** Optimizations should be aborted. No space left on device. */
+    private static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
+    /** Optimizations should be aborted. Thermal throttling level too high. */
+    private static final int STATUS_ABORT_THERMAL = 3;
+    /** Battery level too low */
+    private static final int STATUS_ABORT_BATTERY = 4;
+    /** {@link PackageDexOptimizer#DEX_OPT_FAILED} case */
+    private static final int STATUS_DEX_OPT_FAILED = 5;
 
-    // Optimizations finished. All packages were processed.
-    private static final int OPTIMIZE_PROCESSED = 0;
-    // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
-    private static final int OPTIMIZE_CONTINUE = 1;
-    // Optimizations should be aborted. Job scheduler requested it.
-    private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
-    // Optimizations should be aborted. No space left on device.
-    private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
-    // Optimizations should be aborted. Thermal throttling level too high.
-    private static final int OPTIMIZE_ABORT_THERMAL = 4;
+    @IntDef(prefix = {"STATUS_"}, value = {
+            STATUS_OK,
+            STATUS_ABORT_BY_CANCELLATION,
+            STATUS_ABORT_NO_SPACE_LEFT,
+            STATUS_ABORT_THERMAL,
+            STATUS_ABORT_BATTERY,
+            STATUS_DEX_OPT_FAILED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Status {
+    }
 
     // Used for calculating space threshold for downgrading unused apps.
     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
@@ -94,87 +119,360 @@
     // Thermal cutoff value used if one isn't defined by a system property.
     private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
 
+    private final Injector mInjector;
+
+    private final Object mLock = new Object();
+
+    // Thread currently running dexopt. This will be null if dexopt is not running.
+    // The thread running dexopt make sure to set this into null when the pending dexopt is
+    // completed.
+    @GuardedBy("mLock")
+    @Nullable
+    private Thread mDexOptThread;
+
+    // Thread currently cancelling dexopt. This thread is in blocked wait state until
+    // cancellation is done. Only this thread can change states for control. The other threads, if
+    // need to wait for cancellation, should just wait without doing any control.
+    @GuardedBy("mLock")
+    @Nullable
+    private Thread mDexOptCancellingThread;
+
+    // Tells whether post boot update is completed or not.
+    @GuardedBy("mLock")
+    private boolean mFinishedPostBootUpdate;
+
+    @GuardedBy("mLock")
+    @Status private int mLastExecutionStatus = STATUS_OK;
+
+    // Keeps packages cancelled from PDO for last session. This is for debugging.
+    @GuardedBy("mLock")
+    private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
+
     /**
      * Set of failed packages remembered across job runs.
      */
-    static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
-    static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
+    @GuardedBy("mLock")
+    private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
+    @GuardedBy("mLock")
+    private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
 
-    /**
-     * Atomics set to true if the JobScheduler requests an abort.
-     */
-    private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
-    private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+    private final long mDowngradeUnusedAppsThresholdInMillis;
 
-    /**
-     * Atomic set to true if one job should exit early because another job was started.
-     */
-    private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
-
-    private final File mDataDir = Environment.getDataDirectory();
-    private static final long mDowngradeUnusedAppsThresholdInMillis =
-            getDowngradeUnusedAppsThresholdInMillis();
-
-    private final IThermalService mThermalService =
-            IThermalService.Stub.asInterface(
-                ServiceManager.getService(Context.THERMAL_SERVICE));
-
-    private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+    private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
 
     private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
 
-    public static void schedule(Context context) {
-        if (isBackgroundDexoptDisabled()) {
+    /** Listener for monitoring package change due to dexopt. */
+    public interface PackagesUpdatedListener {
+        /** Called when the packages are updated through dexopt */
+        void onPackagesUpdated(ArraySet<String> updatedPackages);
+    }
+
+    public BackgroundDexOptService(Context context, DexManager dexManager) {
+        this(new Injector(context, dexManager));
+    }
+
+    @VisibleForTesting
+    public BackgroundDexOptService(Injector injector) {
+        mInjector = injector;
+        LocalServices.addService(BackgroundDexOptService.class, this);
+        mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
+    }
+
+    /** Start scheduling job after boot completion */
+    public void systemReady() {
+        if (mInjector.isBackgroundDexOptDisabled()) {
             return;
         }
 
-        final JobScheduler js = context.getSystemService(JobScheduler.class);
-
-        // Schedule a one-off job which scans installed packages and updates
-        // out-of-date oat files. Schedule it 10 minutes after the boot complete event,
-        // so that we don't overload the boot with additional dex2oat compilations.
-        context.registerReceiver(new BroadcastReceiver() {
+        mInjector.getContext().registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
-                        .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
-                        .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
-                        .build());
-                context.unregisterReceiver(this);
+                mInjector.getContext().unregisterReceiver(this);
+                // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
+                // completed.
+                scheduleAJob(JOB_POST_BOOT_UPDATE);
+                scheduleAJob(JOB_IDLE_OPTIMIZE);
                 if (DEBUG) {
-                    Slog.i(TAG, "BootBgDexopt scheduled");
+                    Slog.d(TAG, "BootBgDexopt scheduled");
                 }
             }
         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+    }
 
-        // Schedule a daily job which scans installed packages and compiles
-        // those with fresh profiling data.
-        js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
-                    .setRequiresDeviceIdle(true)
-                    .setRequiresCharging(true)
-                    .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
-                    .build());
-
-        if (DEBUG) {
-            Slog.d(TAG, "BgDexopt scheduled");
+    /** Dump the current state */
+    public void dump(IndentingPrintWriter writer) {
+        boolean disabled = mInjector.isBackgroundDexOptDisabled();
+        writer.print("enabled:");
+        writer.println(!disabled);
+        if (disabled) {
+            return;
+        }
+        synchronized (mLock) {
+            writer.print("mDexOptThread:");
+            writer.println(mDexOptThread);
+            writer.print("mDexOptCancellingThread:");
+            writer.println(mDexOptCancellingThread);
+            writer.print("mFinishedPostBootUpdate:");
+            writer.print(mFinishedPostBootUpdate);
+            writer.print(",mLastExecutionStatus:");
+            writer.println(mLastExecutionStatus);
+            writer.print("mLastCancelledPackages:");
+            writer.println(String.join(",", mLastCancelledPackages));
+            writer.print("mFailedPackageNamesPrimary:");
+            writer.println(String.join(",", mFailedPackageNamesPrimary));
+            writer.print("mFailedPackageNamesSecondary:");
+            writer.println(String.join(",", mFailedPackageNamesSecondary));
         }
     }
 
-    public static void notifyPackageChanged(String packageName) {
-        // The idle maintanance job skips packages which previously failed to
+    /** Gets the instance of the service */
+    public static BackgroundDexOptService getService() {
+        return LocalServices.getService(BackgroundDexOptService.class);
+    }
+
+    /**
+     * Executes the background dexopt job immediately for selected packages or all packages.
+     *
+     * <p>This is only for shell command and only root or shell user can use this.
+     *
+     * @param packageNames dex optimize the passed packages or all packages if null
+     *
+     * @return true if dex optimization is complete. false if the task is cancelled or if there was
+     *         an error.
+     */
+    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
+        enforceRootOrShell();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                // Do not cancel and wait for completion if there is pending task.
+                waitForDexOptThreadToFinishLocked();
+                resetStatesForNewDexOptRunLocked(Thread.currentThread());
+            }
+            PackageManagerService pm = mInjector.getPackageManagerService();
+            ArraySet<String> packagesToOptimize;
+            if (packageNames == null) {
+                packagesToOptimize = pm.getOptimizablePackages();
+            } else {
+                packagesToOptimize = new ArraySet<>(packageNames);
+            }
+            return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+            markDexOptCompleted();
+        }
+    }
+
+    /**
+     * Cancels currently running any idle optimization tasks started from JobScheduler
+     * or runIdleOptimizationsNow call.
+     *
+     * <p>This is only for shell command and only root or shell user can use this.
+     */
+    public void cancelBackgroundDexoptJob() {
+        enforceRootOrShell();
+        Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
+    }
+
+    /** Adds listener for package update */
+    public void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (mLock) {
+            mPackagesUpdatedListeners.add(listener);
+        }
+    }
+
+    /** Removes package update listener */
+    public void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (mLock) {
+            mPackagesUpdatedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Notifies package change and removes the package from the failed package list so that
+     * the package can run dexopt again.
+     */
+    public void notifyPackageChanged(String packageName) {
+        // The idle maintenance job skips packages which previously failed to
         // compile. The given package has changed and may successfully compile
         // now. Remove it from the list of known failing packages.
-        synchronized (sFailedPackageNamesPrimary) {
-            sFailedPackageNamesPrimary.remove(packageName);
-        }
-        synchronized (sFailedPackageNamesSecondary) {
-            sFailedPackageNamesSecondary.remove(packageName);
+        synchronized (mLock) {
+            mFailedPackageNamesPrimary.remove(packageName);
+            mFailedPackageNamesSecondary.remove(packageName);
         }
     }
 
-    private long getLowStorageThreshold(Context context) {
-        @SuppressWarnings("deprecation")
-        final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
+    /** For BackgroundDexOptJobService to dispatch onStartJob event */
+    /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
+        Slog.i(TAG, "onStartJob:" + params.getJobId());
+
+        boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
+        // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
+        // the checks above. This check is not "live" - the value is determined by a background
+        // restart with a period of ~1 minute.
+        PackageManagerService pm = mInjector.getPackageManagerService();
+        if (pm.isStorageLow()) {
+            Slog.w(TAG, "Low storage, skipping this run");
+            markPostBootUpdateCompleted(params);
+            return false;
+        }
+
+        ArraySet<String> pkgs = pm.getOptimizablePackages();
+        if (pkgs.isEmpty()) {
+            Slog.i(TAG, "No packages to optimize");
+            markPostBootUpdateCompleted(params);
+            return false;
+        }
+
+        mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
+
+        synchronized (mLock) {
+            if (mDexOptThread != null && mDexOptThread.isAlive()) {
+                // Other task is already running.
+                return false;
+            }
+            if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
+                // Post boot job not finished yet. Run post boot job first.
+                return false;
+            }
+            resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
+                    "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
+                    () -> {
+                        TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG,
+                                Trace.TRACE_TAG_PACKAGE_MANAGER);
+                        tr.traceBegin("jobExecution");
+                        boolean completed = false;
+                        try {
+                            completed = runIdleOptimization(pm, pkgs,
+                                    params.getJobId() == JOB_POST_BOOT_UPDATE);
+                        } finally { // Those cleanup should be done always.
+                            tr.traceEnd();
+                            Slog.i(TAG, "dexopt finishing. jobid:" + params.getJobId()
+                                    + " completed:" + completed);
+
+                            if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+                                if (completed) {
+                                    markPostBootUpdateCompleted(params);
+                                }
+                                // Reschedule when cancelled
+                                job.jobFinished(params, !completed);
+                            } else {
+                                // Periodic job
+                                job.jobFinished(params, true);
+                            }
+                            markDexOptCompleted();
+                        }
+                    }));
+        }
+        return true;
+    }
+
+    /** For BackgroundDexOptJobService to dispatch onStopJob event */
+    /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
+        Slog.i(TAG, "onStopJob:" + params.getJobId());
+        // This cannot block as it is in main thread, thus dispatch to a newly created thread and
+        // cancel it from there.
+        // As this event does not happen often, creating a new thread is justified rather than
+        // having one thread kept permanently.
+        mInjector.createAndStartThread("DexOptCancel", this::cancelDexOptAndWaitForCompletion);
+        // Always reschedule for cancellation.
+        return true;
+    }
+
+    /**
+     * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
+     * until cancellation is done.
+     */
+    private void cancelDexOptAndWaitForCompletion() {
+        synchronized (mLock) {
+            if (mDexOptThread == null) {
+                return;
+            }
+            if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
+                // No control, just wait
+                waitForDexOptThreadToFinishLocked();
+                // Do not wait for other cancellation's complete. That will be handled by the next
+                // start flow.
+                return;
+            }
+            mDexOptCancellingThread = Thread.currentThread();
+            // Take additional caution to make sure that we do not leave this call
+            // with controlDexOptBlockingLocked(true) state.
+            try {
+                controlDexOptBlockingLocked(true);
+                waitForDexOptThreadToFinishLocked();
+            } finally {
+                // Reset to default states regardless of previous states
+                mDexOptCancellingThread = null;
+                mDexOptThread = null;
+                controlDexOptBlockingLocked(false);
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void waitForDexOptThreadToFinishLocked() {
+        TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+        tr.traceBegin("waitForDexOptThreadToFinishLocked");
+        try {
+            // Wait but check in regular internal to see if the thread is still alive.
+            while (mDexOptThread != null && mDexOptThread.isAlive()) {
+                mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
+            }
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Interrupted while waiting for dexopt thread");
+            Thread.currentThread().interrupt();
+        }
+        tr.traceEnd();
+    }
+
+    private void markDexOptCompleted() {
+        synchronized (mLock) {
+            if (mDexOptThread != Thread.currentThread()) {
+                throw new IllegalStateException(
+                        "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
+                                + " current:" + Thread.currentThread());
+            }
+            mDexOptThread = null;
+            // Other threads may be waiting for completion.
+            mLock.notifyAll();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void resetStatesForNewDexOptRunLocked(Thread thread) {
+        mDexOptThread = thread;
+        mLastCancelledPackages.clear();
+        controlDexOptBlockingLocked(false);
+    }
+
+    private void enforceRootOrShell() {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+            throw new SecurityException("Should be shell or root user");
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void controlDexOptBlockingLocked(boolean block) {
+        mInjector.getPackageManagerService().controlDexOptBlocking(block);
+    }
+
+    private void scheduleAJob(int jobId) {
+        JobScheduler js = mInjector.getJobScheduler();
+        JobInfo.Builder builder = new JobInfo.Builder(jobId, sDexoptServiceName)
+                .setRequiresDeviceIdle(true);
+        if (jobId == JOB_IDLE_OPTIMIZE) {
+            builder.setRequiresCharging(true)
+                    .setPeriodic(IDLE_OPTIMIZATION_PERIOD);
+        }
+        js.schedule(builder.build());
+    }
+
+    private long getLowStorageThreshold() {
+        long lowThreshold = mInjector.getDataDirStorageLowBytes();
         if (lowThreshold == 0) {
             Slog.e(TAG, "Invalid low storage threshold");
         }
@@ -182,125 +480,46 @@
         return lowThreshold;
     }
 
-    private boolean runPostBootUpdate(final JobParameters jobParams,
-            final PackageManagerService pm, final ArraySet<String> pkgs) {
-        if (mExitPostBootUpdate.get()) {
-            // This job has already been superseded. Do not start it.
-            return false;
+    private void logStatus(int status) {
+        switch (status) {
+            case STATUS_OK:
+                Slog.i(TAG, "Idle optimizations completed.");
+                break;
+            case STATUS_ABORT_NO_SPACE_LEFT:
+                Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
+                break;
+            case STATUS_ABORT_BY_CANCELLATION:
+                Slog.w(TAG, "Idle optimizations aborted by cancellation.");
+                break;
+            case STATUS_ABORT_THERMAL:
+                Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
+                break;
+            case STATUS_ABORT_BATTERY:
+                Slog.w(TAG, "Idle optimizations aborted by low battery.");
+                break;
+            case STATUS_DEX_OPT_FAILED:
+                Slog.w(TAG, "Idle optimizations failed from dexopt.");
+                break;
+            default:
+                Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
+                break;
         }
-        new Thread("BackgroundDexOptService_PostBootUpdate") {
-            @Override
-            public void run() {
-                postBootUpdate(jobParams, pm, pkgs);
-            }
-
-        }.start();
-        return true;
     }
 
-    private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
-            ArraySet<String> pkgs) {
-        final BatteryManagerInternal batteryManagerInternal =
-                LocalServices.getService(BatteryManagerInternal.class);
-        final long lowThreshold = getLowStorageThreshold(this);
-
-        mAbortPostBootUpdate.set(false);
-
-        ArraySet<String> updatedPackages = new ArraySet<>();
-        for (String pkg : pkgs) {
-            if (mAbortPostBootUpdate.get()) {
-                // JobScheduler requested an early abort.
-                return;
-            }
-            if (mExitPostBootUpdate.get()) {
-                // Different job, which supersedes this one, is running.
-                break;
-            }
-            if (batteryManagerInternal.getBatteryLevelLow()) {
-                // Rather bail than completely drain the battery.
-                break;
-            }
-            long usableSpace = mDataDir.getUsableSpace();
-            if (usableSpace < lowThreshold) {
-                // Rather bail than completely fill up the disk.
-                Slog.w(TAG, "Aborting background dex opt job due to low storage: " +
-                        usableSpace);
-                break;
-            }
-            if (DEBUG) {
-                Slog.i(TAG, "Updating package " + pkg);
-            }
-
-            // Update package if needed. Note that there can be no race between concurrent
-            // jobs because PackageDexOptimizer.performDexOpt is synchronized.
-
-            // checkProfiles is false to avoid merging profiles during boot which
-            // might interfere with background compilation (b/28612421).
-            // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
-            // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
-            // trade-off worth doing to save boot time work.
-            int result = pm.performDexOptWithStatus(new DexoptOptions(
-                    pkg,
-                    PackageManagerService.REASON_POST_BOOT,
-                    DexoptOptions.DEXOPT_BOOT_COMPLETE));
-            if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
-                updatedPackages.add(pkg);
-            }
+    /** Returns true if completed */
+    private boolean runIdleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
+            boolean isPostBootUpdate) {
+        long lowStorageThreshold = getLowStorageThreshold();
+        int status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
+        logStatus(status);
+        synchronized (mLock) {
+            mLastExecutionStatus = status;
         }
-        notifyPinService(updatedPackages);
-        notifyPackagesUpdated(updatedPackages);
-        // Ran to completion, so we abandon our timeslice and do not reschedule.
-        jobFinished(jobParams, /* reschedule */ false);
+
+        return status == STATUS_OK;
     }
 
-    private boolean runIdleOptimization(final JobParameters jobParams,
-            final PackageManagerService pm, final ArraySet<String> pkgs) {
-        new Thread("BackgroundDexOptService_IdleOptimization") {
-            @Override
-            public void run() {
-                int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
-                if (result == OPTIMIZE_PROCESSED) {
-                    Slog.i(TAG, "Idle optimizations completed.");
-                } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
-                    Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
-                } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-                    Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
-                } else if (result == OPTIMIZE_ABORT_THERMAL) {
-                    Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
-                } else {
-                    Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
-                }
-
-                if (result == OPTIMIZE_ABORT_THERMAL) {
-                    // Abandon our timeslice and reschedule
-                    jobFinished(jobParams, /* wantsReschedule */ true);
-                } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
-                    // Abandon our timeslice and do not reschedule.
-                    jobFinished(jobParams, /* wantsReschedule */ false);
-                }
-            }
-        }.start();
-        return true;
-    }
-
-    // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
-    private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
-            Context context) {
-        Slog.i(TAG, "Performing idle optimizations");
-        // If post-boot update is still running, request that it exits early.
-        mExitPostBootUpdate.set(true);
-        mAbortIdleOptimization.set(false);
-
-        long lowStorageThreshold = getLowStorageThreshold(context);
-        int result = idleOptimizePackages(pm, pkgs, lowStorageThreshold);
-        return result;
-    }
-
-    /**
-     * Get the size of the directory. It uses recursion to go over all files.
-     * @param f
-     * @return
-     */
+    /** Gets the size of the directory. It uses recursion to go over all files. */
     private long getDirectorySize(File f) {
         long size = 0;
         if (f.isDirectory()) {
@@ -313,10 +532,7 @@
         return size;
     }
 
-    /**
-     * Get the size of a package.
-     * @param pkg
-     */
+    /** Gets the size of a package. */
     private long getPackageSize(PackageManagerService pm, String pkg) {
         PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
         long size = 0;
@@ -340,17 +556,18 @@
         return 0;
     }
 
+    @Status
     private int idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
-            long lowStorageThreshold) {
+            long lowStorageThreshold, boolean isPostBootUpdate) {
         ArraySet<String> updatedPackages = new ArraySet<>();
         ArraySet<String> updatedPackagesDueToSecondaryDex = new ArraySet<>();
 
         try {
-            final boolean supportSecondaryDex = supportSecondaryDex();
+            boolean supportSecondaryDex = mInjector.supportSecondaryDex();
 
             if (supportSecondaryDex) {
-                int result = reconcileSecondaryDexFiles(pm.getDexManager());
-                if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                @Status int result = reconcileSecondaryDexFiles();
+                if (result != STATUS_OK) {
                     return result;
                 }
             }
@@ -358,7 +575,7 @@
             // Only downgrade apps when space is low on device.
             // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
             // up disk before user hits the actual lowStorageThreshold.
-            final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
+            long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
                     * lowStorageThreshold;
             boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
             if (DEBUG) {
@@ -368,21 +585,33 @@
                 Set<String> unusedPackages =
                         pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
                 if (DEBUG) {
-                    Slog.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
+                    Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
                 }
 
                 if (!unusedPackages.isEmpty()) {
                     for (String pkg : unusedPackages) {
-                        int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
-                        if (abortCode != OPTIMIZE_CONTINUE) {
+                        @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
+                        if (abortCode != STATUS_OK) {
                             // Should be aborted by the scheduler.
                             return abortCode;
                         }
-                        if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) {
+                        @DexOptResult int downgradeResult = downgradePackage(pm, pkg,
+                                /* isForPrimaryDex= */ true, isPostBootUpdate);
+                        if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
                             updatedPackages.add(pkg);
                         }
+                        @Status int status = convertPackageDexOptimizerStatusToInternal(
+                                downgradeResult);
+                        if (status != STATUS_OK) {
+                            return status;
+                        }
                         if (supportSecondaryDex) {
-                            downgradePackage(pm, pkg, /*isForPrimaryDex*/ false);
+                            downgradeResult = downgradePackage(pm, pkg,
+                                    /* isForPrimaryDex= */false, isPostBootUpdate);
+                            status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
+                            if (status != STATUS_OK) {
+                                return status;
+                            }
                         }
                     }
 
@@ -391,18 +620,19 @@
                 }
             }
 
-            int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
-                    /*isForPrimaryDex*/ true, updatedPackages);
-            if (primaryResult != OPTIMIZE_PROCESSED) {
+            @Status int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+                    /*isForPrimaryDex=*/ true, updatedPackages, isPostBootUpdate);
+            if (primaryResult != STATUS_OK) {
                 return primaryResult;
             }
 
             if (!supportSecondaryDex) {
-                return OPTIMIZE_PROCESSED;
+                return STATUS_OK;
             }
 
-            int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
-                    /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex);
+            @Status int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+                    /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex,
+                    isPostBootUpdate);
             return secondaryResult;
         } finally {
             // Always let the pinner service know about changes.
@@ -414,21 +644,25 @@
         }
     }
 
+    @Status
     private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
-            long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) {
+            long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages,
+            boolean isPostBootUpdate) {
         for (String pkg : pkgs) {
             int abortCode = abortIdleOptimizations(lowStorageThreshold);
-            if (abortCode != OPTIMIZE_CONTINUE) {
+            if (abortCode != STATUS_OK) {
                 // Either aborted by the scheduler or no space left.
                 return abortCode;
             }
 
-            boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex);
-            if (dexOptPerformed) {
+            @DexOptResult int result = optimizePackage(pm, pkg, isForPrimaryDex, isPostBootUpdate);
+            if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
                 updatedPackages.add(pkg);
+            } else if (result != PackageDexOptimizer.DEX_OPT_SKIPPED) {
+                return convertPackageDexOptimizerStatusToInternal(result);
             }
         }
-        return OPTIMIZE_PROCESSED;
+        return STATUS_OK;
     }
 
     /**
@@ -436,21 +670,25 @@
      * eg. if the package is in speed-profile the package will be downgraded to verify.
      * @param pm PackageManagerService
      * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
-     * @return true if the package was downgraded.
+     * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+     * @return PackageDexOptimizer.DEX_*
      */
-    private boolean downgradePackage(PackageManagerService pm, String pkg,
-            boolean isForPrimaryDex) {
+    @DexOptResult
+    private int downgradePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex, boolean isPostBootUpdate) {
         if (DEBUG) {
             Slog.d(TAG, "Downgrading " + pkg);
         }
-        boolean dex_opt_performed = false;
+        if (isCancelling()) {
+            return PackageDexOptimizer.DEX_OPT_CANCELLED;
+        }
         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
-        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
-                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
-                | DexoptOptions.DEXOPT_DOWNGRADE;
+        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
+        if (!isPostBootUpdate) {
+            dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+        }
         long package_size_before = getPackageSize(pm, pkg);
-
+        int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
         if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
             // This applies for system apps or if packages location is not a directory, i.e.
             // monolithic install.
@@ -459,32 +697,29 @@
                 // remove their compiler artifacts from dalvik cache.
                 pm.deleteOatArtifactsOfPackage(pkg);
             } else {
-                dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+                result = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
             }
         } else {
-            dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+            result = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
         }
 
-        if (dex_opt_performed) {
+        if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
             FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
                     getPackageSize(pm, pkg), /*aggressive=*/ false);
         }
-        return dex_opt_performed;
+        return result;
     }
 
-    private boolean supportSecondaryDex() {
-        return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
-    }
-
-    private int reconcileSecondaryDexFiles(DexManager dm) {
+    @Status
+    private int reconcileSecondaryDexFiles() {
         // TODO(calin): should we denylist packages for which we fail to reconcile?
-        for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
-            if (mAbortIdleOptimization.get()) {
-                return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+        for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
+            if (isCancelling()) {
+                return STATUS_ABORT_BY_CANCELLATION;
             }
-            dm.reconcileSecondaryDexFiles(p);
+            mInjector.getDexManager().reconcileSecondaryDexFiles(p);
         }
-        return OPTIMIZE_PROCESSED;
+        return STATUS_OK;
     }
 
     /**
@@ -493,39 +728,46 @@
      * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
      * @param pm An instance of PackageManagerService
      * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
-     * @return true if the package was downgraded.
+     * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+     * @param isPostBootUpdate is post boot update or not.
+     * @return PackageDexOptimizer#DEX_OPT_*
      */
-    private boolean optimizePackage(PackageManagerService pm, String pkg,
-            boolean isForPrimaryDex) {
-        int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
-        int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
-                | DexoptOptions.DEXOPT_BOOT_COMPLETE
-                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+    @DexOptResult
+    private int optimizePackage(PackageManagerService pm, String pkg,
+            boolean isForPrimaryDex, boolean isPostBootUpdate) {
+        int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
+                : PackageManagerService.REASON_BACKGROUND_DEXOPT;
+        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
+        if (!isPostBootUpdate) {
+            dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+                    | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+        }
 
         // System server share the same code path as primary dex files.
         // PackageManagerService will select the right optimization path for it.
-        return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg))
-            ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
-            : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+        if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
+            return performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+        } else {
+            return performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+        }
     }
 
-    private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+    @DexOptResult
+    private int performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
             int dexoptFlags) {
-        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+        return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
                 () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
-        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
     }
 
-    private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+    @DexOptResult
+    private int performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
             int dexoptFlags) {
         DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
                 dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
-        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+        return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
                 () -> pm.performDexOpt(dexoptOptions)
                     ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
         );
-        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
     }
 
     /**
@@ -533,194 +775,212 @@
      * the package is added to the list of failed packages.
      * Return one of following result:
      *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+     *  {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
      *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
      *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
      */
+    @DexOptResult
     private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
             Supplier<Integer> performDexOptWrapper) {
-        ArraySet<String> sFailedPackageNames =
-                isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
-        synchronized (sFailedPackageNames) {
-            if (sFailedPackageNames.contains(pkg)) {
+        ArraySet<String> failedPackageNames;
+        synchronized (mLock) {
+            failedPackageNames =
+                    isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
+            if (failedPackageNames.contains(pkg)) {
                 // Skip previously failing package
                 return PackageDexOptimizer.DEX_OPT_SKIPPED;
             }
-            sFailedPackageNames.add(pkg);
         }
         int result = performDexOptWrapper.get();
-        if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
-            synchronized (sFailedPackageNames) {
-                sFailedPackageNames.remove(pkg);
+        if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+            synchronized (mLock) {
+                failedPackageNames.add(pkg);
+            }
+        } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
+            synchronized (mLock) {
+                mLastCancelledPackages.add(pkg);
             }
         }
         return result;
     }
 
-    // Evaluate whether or not idle optimizations should continue.
+    @Status
+    private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
+        switch (pdoStatus) {
+            case PackageDexOptimizer.DEX_OPT_CANCELLED:
+                return STATUS_ABORT_BY_CANCELLATION;
+            case PackageDexOptimizer.DEX_OPT_FAILED:
+                return STATUS_DEX_OPT_FAILED;
+            case PackageDexOptimizer.DEX_OPT_PERFORMED:
+            case PackageDexOptimizer.DEX_OPT_SKIPPED:
+                return STATUS_OK;
+            default:
+                Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
+                        new RuntimeException());
+                return STATUS_DEX_OPT_FAILED;
+        }
+    }
+
+    /** Evaluate whether or not idle optimizations should continue. */
+    @Status
     private int abortIdleOptimizations(long lowStorageThreshold) {
-        if (mAbortIdleOptimization.get()) {
+        if (isCancelling()) {
             // JobScheduler requested an early abort.
-            return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+            return STATUS_ABORT_BY_CANCELLATION;
         }
 
         // Abort background dexopt if the device is in a moderate or stronger thermal throttling
         // state.
-        try {
-            final int thermalStatus = mThermalService.getCurrentThermalStatus();
-
-            if (DEBUG) {
-                Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
-            }
-
-            if (thermalStatus >= mThermalStatusCutoff) {
-                return OPTIMIZE_ABORT_THERMAL;
-            }
-        } catch (RemoteException ex) {
-            // Because this is a intra-process Binder call it is impossible for a RemoteException
-            // to be raised.
+        int thermalStatus = mInjector.getCurrentThermalStatus();
+        if (DEBUG) {
+            Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
+        }
+        if (thermalStatus >= mThermalStatusCutoff) {
+            return STATUS_ABORT_THERMAL;
         }
 
-        long usableSpace = mDataDir.getUsableSpace();
+        if (mInjector.isBatteryLevelLow()) {
+            return STATUS_ABORT_BATTERY;
+        }
+
+        long usableSpace = mInjector.getDataDirUsableSpace();
         if (usableSpace < lowStorageThreshold) {
             // Rather bail than completely fill up the disk.
             Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
-            return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+            return STATUS_ABORT_NO_SPACE_LEFT;
         }
 
-        return OPTIMIZE_CONTINUE;
+        return STATUS_OK;
     }
 
     // Evaluate whether apps should be downgraded.
     private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
-        long usableSpace = mDataDir.getUsableSpace();
-        if (usableSpace < lowStorageThresholdForDowngrade) {
+        if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
             return true;
         }
 
         return false;
     }
 
-    /**
-     * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
-     * then execute on all packages.
-     */
-    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
-            @Nullable List<String> packageNames) {
-        // Create a new object to make sure we don't interfere with the scheduled jobs.
-        // Note that this may still run at the same time with the job scheduled by the
-        // JobScheduler but the scheduler will not be able to cancel it.
-        BackgroundDexOptService bdos = new BackgroundDexOptService();
-        ArraySet<String> packagesToOptimize;
-        if (packageNames == null) {
-            packagesToOptimize = pm.getOptimizablePackages();
-        } else {
-            packagesToOptimize = new ArraySet<>(packageNames);
+    private boolean isCancelling() {
+        synchronized (mLock) {
+            return mDexOptCancellingThread != null;
         }
-        int result = bdos.idleOptimization(pm, packagesToOptimize, context);
-        return result == OPTIMIZE_PROCESSED;
     }
 
-    @Override
-    public boolean onStartJob(JobParameters params) {
-        if (DEBUG) {
-            Slog.i(TAG, "onStartJob");
+    private void markPostBootUpdateCompleted(JobParameters params) {
+        if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
+            return;
         }
-
-        // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
-        // the checks above. This check is not "live" - the value is determined by a background
-        // restart with a period of ~1 minute.
-        PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
-        if (pm.isStorageLow()) {
-            Slog.i(TAG, "Low storage, skipping this run");
-            return false;
-        }
-
-        final ArraySet<String> pkgs = pm.getOptimizablePackages();
-        if (pkgs.isEmpty()) {
-            Slog.i(TAG, "No packages to optimize");
-            return false;
-        }
-
-        mThermalStatusCutoff =
-            SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
-
-        boolean result;
-        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
-            result = runPostBootUpdate(params, pm, pkgs);
-        } else {
-            result = runIdleOptimization(params, pm, pkgs);
-        }
-
-        return result;
-    }
-
-    @Override
-    public boolean onStopJob(JobParameters params) {
-        if (DEBUG) {
-            Slog.d(TAG, "onStopJob");
-        }
-
-        if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
-            mAbortPostBootUpdate.set(true);
-
-            // Do not reschedule.
-            // TODO: We should reschedule if we didn't process all apps, yet.
-            return false;
-        } else {
-            mAbortIdleOptimization.set(true);
-
-            // Reschedule the run.
-            // TODO: Should this be dependent on the stop reason?
-            return true;
+        synchronized (mLock) {
+            if (!mFinishedPostBootUpdate) {
+                mFinishedPostBootUpdate = true;
+                JobScheduler js = mInjector.getJobScheduler();
+                js.cancel(JOB_POST_BOOT_UPDATE);
+            }
         }
     }
 
     private void notifyPinService(ArraySet<String> updatedPackages) {
-        PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+        PinnerService pinnerService = mInjector.getPinnerService();
         if (pinnerService != null) {
             Slog.i(TAG, "Pinning optimized code " + updatedPackages);
             pinnerService.update(updatedPackages, false /* force */);
         }
     }
 
-    public static interface PackagesUpdatedListener {
-        /** Callback when packages have been updated by the bg-dexopt service. */
-        public void onPackagesUpdated(ArraySet<String> updatedPackages);
-    }
-
-    public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
-        synchronized (sPackagesUpdatedListeners) {
-            sPackagesUpdatedListeners.add(listener);
-        }
-    }
-
-    public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
-        synchronized (sPackagesUpdatedListeners) {
-            sPackagesUpdatedListeners.remove(listener);
-        }
-    }
-
     /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
     private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
-        synchronized (sPackagesUpdatedListeners) {
-            for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
+        synchronized (mLock) {
+            for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
                 listener.onPackagesUpdated(updatedPackages);
             }
         }
     }
 
-    private static long getDowngradeUnusedAppsThresholdInMillis() {
-        final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
-        String sysPropValue = SystemProperties.get(sysPropKey);
-        if (sysPropValue == null || sysPropValue.isEmpty()) {
-            Slog.w(TAG, "SysProp " + sysPropKey + " not set");
-            return Long.MAX_VALUE;
-        }
-        return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
-    }
+    /** Injector pattern for testing purpose */
+    @VisibleForTesting
+    static final class Injector {
+        private final Context mContext;
+        private final DexManager mDexManager;
+        private final File mDataDir = Environment.getDataDirectory();
 
-    private static boolean isBackgroundDexoptDisabled() {
-        return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
-                false /* default */);
+        Injector(Context context, DexManager dexManager) {
+            mContext = context;
+            mDexManager = dexManager;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        PackageManagerService getPackageManagerService() {
+            return (PackageManagerService) ServiceManager.getService("package");
+        }
+
+        JobScheduler getJobScheduler() {
+            return mContext.getSystemService(JobScheduler.class);
+        }
+
+        DexManager getDexManager() {
+            return mDexManager;
+        }
+
+        PinnerService getPinnerService() {
+            return LocalServices.getService(PinnerService.class);
+        }
+
+        boolean isBackgroundDexOptDisabled() {
+            return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
+                    false /* default */);
+        }
+
+        boolean isBatteryLevelLow() {
+            return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
+        }
+
+        long getDowngradeUnusedAppsThresholdInMillis() {
+            String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
+            String sysPropValue = SystemProperties.get(sysPropKey);
+            if (sysPropValue == null || sysPropValue.isEmpty()) {
+                Slog.w(TAG, "SysProp " + sysPropKey + " not set");
+                return Long.MAX_VALUE;
+            }
+            return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
+        }
+
+        boolean supportSecondaryDex() {
+            return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+        }
+
+        long getDataDirUsableSpace() {
+            return mDataDir.getUsableSpace();
+        }
+
+        long getDataDirStorageLowBytes() {
+            return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
+        }
+
+        int getCurrentThermalStatus() {
+            IThermalService thermalService = IThermalService.Stub
+                    .asInterface(ServiceManager.getService(Context.THERMAL_SERVICE));
+            try {
+                return thermalService.getCurrentThermalStatus();
+            } catch (RemoteException e) {
+                return STATUS_ABORT_THERMAL;
+            }
+        }
+
+        int getDexOptThermalCutoff() {
+            return SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff",
+                    THERMAL_CUTOFF_DEFAULT);
+        }
+
+        Thread createAndStartThread(String name, Runnable target) {
+            Thread thread = new Thread(target, name);
+            Slog.i(TAG, "Starting thread:" + name);
+            thread.start();
+            return thread;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 34e9428..aa4d6bb 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -179,6 +179,7 @@
     private final PackageDexOptimizer mPackageDexOptimizer;
     private final DexManager mDexManager;
     private final CompilerStats mCompilerStats;
+    private final BackgroundDexOptService mBackgroundDexOptService;
 
     // PackageManagerService attributes that are primitives are referenced through the
     // pms object directly.  Primitives are the only attributes so referenced.
@@ -228,6 +229,7 @@
         mPackageDexOptimizer = args.service.mPackageDexOptimizer;
         mDexManager = args.service.getDexManager();
         mCompilerStats = args.service.mCompilerStats;
+        mBackgroundDexOptService = args.service.mBackgroundDexOptService;
 
         // Used to reference PMS attributes that are primitives and which are not
         // updated under control of the PMS lock.
@@ -1809,7 +1811,7 @@
 
     @Nullable
     public final SharedLibraryInfo getSharedLibraryInfoLPr(String name, long version) {
-        return PackageManagerService.getSharedLibraryInfo(
+        return SharedLibraryHelper.getSharedLibraryInfo(
                 name, version, mSharedLibraries, null);
     }
 
@@ -2929,6 +2931,10 @@
                             mDexManager.getPackageUseInfoOrDefault(pkgName));
                     ipw.decreaseIndent();
                 }
+                ipw.println("BgDexopt state:");
+                ipw.increaseIndent();
+                mBackgroundDexOptService.dump(ipw);
+                ipw.decreaseIndent();
                 break;
             }
 
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 7dd02cb..40668d7 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -37,6 +37,7 @@
 import android.annotation.Nullable;
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInstaller;
@@ -50,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -73,6 +75,7 @@
  * Relies on RemovePackageHelper to clear internal data structures.
  */
 final class DeletePackageHelper {
+    private static final boolean DEBUG_CLEAN_APKS = false;
     // ------- apps on sdcard specific code -------
     private static final boolean DEBUG_SD_INSTALL = false;
 
@@ -460,6 +463,7 @@
         final SharedUserSetting sus = ps.getSharedUser();
         final List<AndroidPackage> sharedUserPkgs =
                 sus != null ? sus.getPackages() : Collections.emptyList();
+        final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
         final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
                 : new int[] {userId};
         for (int nextUserId : userIds) {
@@ -473,7 +477,8 @@
             }
             PackageManagerService.removeKeystoreDataIfNeeded(mUserManagerInternal, nextUserId,
                     ps.getAppId());
-            mPm.clearPackagePreferredActivities(ps.getPackageName(), nextUserId);
+            preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
+                    nextUserId);
             mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
         }
         mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), pkg,
@@ -509,9 +514,9 @@
 
         // Delete application code and resources only for parent packages
         if (deleteCodeAndResources && (outInfo != null)) {
-            outInfo.mArgs = mPm.createInstallArgsForExisting(
+            outInfo.mArgs = new FileInstallArgs(
                     ps.getPathString(), getAppDexInstructionSets(
-                            ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()));
+                            ps.getPrimaryCpuAbi(), ps.getSecondaryCpuAbi()), mPm);
             if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
         }
     }
@@ -811,4 +816,89 @@
             this.installed = installed;
         }
     }
+
+    /**
+     * We're removing userId and would like to remove any downloaded packages
+     * that are no longer in use by any other user.
+     * @param userId the user being removed
+     */
+    @GuardedBy("mPm.mLock")
+    public void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
+        int [] users = userManager.getUserIds();
+        final int numPackages = mPm.mSettings.getPackagesLocked().size();
+        for (int index = 0; index < numPackages; index++) {
+            final PackageSetting ps = mPm.mSettings.getPackagesLocked().valueAt(index);
+            if (ps.getPkg() == null) {
+                continue;
+            }
+            final String packageName = ps.getPkg().getPackageName();
+            // Skip over if system app or static shared library
+            if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
+                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())) {
+                continue;
+            }
+            if (DEBUG_CLEAN_APKS) {
+                Slog.i(TAG, "Checking package " + packageName);
+            }
+            boolean keep = mPm.shouldKeepUninstalledPackageLPr(packageName);
+            if (keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Keeping package " + packageName + " - requested by DO");
+                }
+            } else {
+                for (int i = 0; i < users.length; i++) {
+                    if (users[i] != userId && ps.getInstalled(users[i])) {
+                        keep = true;
+                        if (DEBUG_CLEAN_APKS) {
+                            Slog.i(TAG, "  Keeping package " + packageName + " for user "
+                                    + users[i]);
+                        }
+                        break;
+                    }
+                }
+            }
+            if (!keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Removing package " + packageName);
+                }
+                //end run
+                mPm.mHandler.post(() -> deletePackageX(
+                        packageName, PackageManager.VERSION_CODE_HIGHEST,
+                        userId, 0, true /*removedBySystem*/));
+            }
+        }
+    }
+
+    public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
+            final IPackageDeleteObserver2 observer, final int userId) {
+        mPm.mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.DELETE_PACKAGES, null);
+        Preconditions.checkNotNull(versionedPackage);
+        Preconditions.checkNotNull(observer);
+        final String packageName = versionedPackage.getPackageName();
+        final long versionCode = versionedPackage.getLongVersionCode();
+
+        int installedForUsersCount = 0;
+        synchronized (mPm.mLock) {
+            // Normalize package name to handle renamed packages and static libs
+            final String internalPkgName = mPm.resolveInternalPackageNameLPr(packageName,
+                    versionCode);
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(internalPkgName);
+            if (ps != null) {
+                int[] installedUsers = ps.queryInstalledUsers(mUserManagerInternal.getUserIds(),
+                        true);
+                installedForUsersCount = installedUsers.length;
+            }
+        }
+
+        if (installedForUsersCount > 1) {
+            deletePackageVersionedInternal(versionedPackage, observer, userId, 0, true);
+        } else {
+            try {
+                observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+                        null);
+            } catch (RemoteException re) {
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
new file mode 100644
index 0000000..d43b681
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -0,0 +1,757 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
+
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+
+import android.content.ComponentName;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.SharedLibraryInfo;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.os.incremental.PerUidReadTimeouts;
+import android.service.pm.PackageServiceDumpProto;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Dumps PackageManagerService internal states.
+ */
+final class DumpHelper {
+    final PackageManagerService mPm;
+
+    DumpHelper(PackageManagerService pm) {
+        mPm = pm;
+    }
+
+    public void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        DumpState dumpState = new DumpState();
+        ArraySet<String> permissionNames = null;
+
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+
+            if ("-a".equals(opt)) {
+                // Right now we only know how to print all.
+            } else if ("-h".equals(opt)) {
+                printHelp(pw);
+                return;
+            } else if ("--checkin".equals(opt)) {
+                dumpState.setCheckIn(true);
+            } else if ("--all-components".equals(opt)) {
+                dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
+            } else if ("-f".equals(opt)) {
+                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+            } else if ("--proto".equals(opt)) {
+                dumpProto(fd);
+                return;
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+
+        // Is the caller requesting to dump a particular piece of data?
+        if (opti < args.length) {
+            String cmd = args[opti];
+            opti++;
+            // Is this a package name?
+            if ("android".equals(cmd) || cmd.contains(".")) {
+                dumpState.setTargetPackageName(cmd);
+                // When dumping a single package, we always dump all of its
+                // filter information since the amount of data will be reasonable.
+                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+            } else if ("check-permission".equals(cmd)) {
+                if (opti >= args.length) {
+                    pw.println("Error: check-permission missing permission argument");
+                    return;
+                }
+                String perm = args[opti];
+                opti++;
+                if (opti >= args.length) {
+                    pw.println("Error: check-permission missing package argument");
+                    return;
+                }
+
+                String pkg = args[opti];
+                opti++;
+                int user = UserHandle.getUserId(Binder.getCallingUid());
+                if (opti < args.length) {
+                    try {
+                        user = Integer.parseInt(args[opti]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: check-permission user argument is not a number: "
+                                + args[opti]);
+                        return;
+                    }
+                }
+
+                // Normalize package name to handle renamed packages and static libs
+                pkg = mPm.resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
+
+                pw.println(mPm.checkPermission(perm, pkg, user));
+                return;
+            } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_LIBS);
+            } else if ("f".equals(cmd) || "features".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_FEATURES);
+            } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
+                if (opti >= args.length) {
+                    dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
+                            | DumpState.DUMP_SERVICE_RESOLVERS
+                            | DumpState.DUMP_RECEIVER_RESOLVERS
+                            | DumpState.DUMP_CONTENT_RESOLVERS);
+                } else {
+                    while (opti < args.length) {
+                        String name = args[opti];
+                        if ("a".equals(name) || "activity".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
+                        } else if ("s".equals(name) || "service".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
+                        } else if ("r".equals(name) || "receiver".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
+                        } else if ("c".equals(name) || "content".equals(name)) {
+                            dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
+                        } else {
+                            pw.println("Error: unknown resolver table type: " + name);
+                            return;
+                        }
+                        opti++;
+                    }
+                }
+            } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PERMISSIONS);
+            } else if ("permission".equals(cmd)) {
+                if (opti >= args.length) {
+                    pw.println("Error: permission requires permission name");
+                    return;
+                }
+                permissionNames = new ArraySet<>();
+                while (opti < args.length) {
+                    permissionNames.add(args[opti]);
+                    opti++;
+                }
+                dumpState.setDump(DumpState.DUMP_PERMISSIONS
+                        | DumpState.DUMP_PACKAGES | DumpState.DUMP_SHARED_USERS);
+            } else if ("pref".equals(cmd) || "preferred".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PREFERRED);
+            } else if ("preferred-xml".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
+                if (opti < args.length && "--full".equals(args[opti])) {
+                    dumpState.setFullPreferred(true);
+                    opti++;
+                }
+            } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
+            } else if ("p".equals(cmd) || "packages".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PACKAGES);
+            } else if ("q".equals(cmd) || "queries".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_QUERIES);
+            } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SHARED_USERS);
+                if (opti < args.length && "noperm".equals(args[opti])) {
+                    dumpState.setOptionEnabled(DumpState.OPTION_SKIP_PERMISSIONS);
+                }
+            } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PROVIDERS);
+            } else if ("m".equals(cmd) || "messages".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_MESSAGES);
+            } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_VERIFIERS);
+            } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
+            } else if ("version".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_VERSION);
+            } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_KEYSETS);
+            } else if ("installs".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_INSTALLS);
+            } else if ("frozen".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_FROZEN);
+            } else if ("volumes".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_VOLUMES);
+            } else if ("dexopt".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_DEXOPT);
+            } else if ("compiler-stats".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
+            } else if ("changes".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_CHANGES);
+            } else if ("service-permissions".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
+            } else if ("known-packages".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
+            } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
+            } else if ("snapshot".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
+                if (opti < args.length) {
+                    if ("--full".equals(args[opti])) {
+                        dumpState.setBrief(false);
+                        opti++;
+                    } else if ("--brief".equals(args[opti])) {
+                        dumpState.setBrief(true);
+                        opti++;
+                    }
+                }
+            } else if ("protected-broadcasts".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PROTECTED_BROADCASTS);
+            } else if ("write".equals(cmd)) {
+                synchronized (mPm.mLock) {
+                    mPm.writeSettingsLPrTEMP();
+                    pw.println("Settings written.");
+                    return;
+                }
+            }
+        }
+
+        final String packageName = dumpState.getTargetPackageName();
+        final boolean checkin = dumpState.isCheckIn();
+
+        // Return if the package doesn't exist.
+        if (packageName != null
+                && mPm.getPackageSetting(packageName) == null
+                && !mPm.mApexManager.isApexPackage(packageName)) {
+            pw.println("Unable to find package: " + packageName);
+            return;
+        }
+
+        if (checkin) {
+            pw.println("vers,1");
+        }
+
+        // reader
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_VERSION)
+                && packageName == null) {
+            mPm.dumpComputer(DumpState.DUMP_VERSION, fd, pw, dumpState);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+            ipw.println("Known Packages:");
+            ipw.increaseIndent();
+            for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) {
+                final String knownPackage = PackageManagerInternal.knownPackageToString(i);
+                ipw.print(knownPackage);
+                ipw.println(":");
+                final String[] pkgNames = mPm.getKnownPackageNamesInternal(i,
+                        UserHandle.USER_SYSTEM);
+                ipw.increaseIndent();
+                if (ArrayUtils.isEmpty(pkgNames)) {
+                    ipw.println("none");
+                } else {
+                    for (String name : pkgNames) {
+                        ipw.println(name);
+                    }
+                }
+                ipw.decreaseIndent();
+            }
+            ipw.decreaseIndent();
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_VERIFIERS)
+                && packageName == null) {
+            final String requiredVerifierPackage = mPm.mRequiredVerifierPackage;
+            if (!checkin) {
+                if (dumpState.onTitlePrinted()) {
+                    pw.println();
+                }
+                pw.println("Verifiers:");
+                pw.print("  Required: ");
+                pw.print(requiredVerifierPackage);
+                pw.print(" (uid=");
+                pw.print(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+                        UserHandle.USER_SYSTEM));
+                pw.println(")");
+            } else if (requiredVerifierPackage != null) {
+                pw.print("vrfy,"); pw.print(requiredVerifierPackage);
+                pw.print(",");
+                pw.println(mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+                        UserHandle.USER_SYSTEM));
+            }
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER)
+                && packageName == null) {
+            final DomainVerificationProxy proxy = mPm.mDomainVerificationManager.getProxy();
+            final ComponentName verifierComponent = proxy.getComponentName();
+            if (verifierComponent != null) {
+                String verifierPackageName = verifierComponent.getPackageName();
+                if (!checkin) {
+                    if (dumpState.onTitlePrinted()) {
+                        pw.println();
+                    }
+                    pw.println("Domain Verifier:");
+                    pw.print("  Using: ");
+                    pw.print(verifierPackageName);
+                    pw.print(" (uid=");
+                    pw.print(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
+                            UserHandle.USER_SYSTEM));
+                    pw.println(")");
+                } else if (verifierPackageName != null) {
+                    pw.print("dv,"); pw.print(verifierPackageName);
+                    pw.print(",");
+                    pw.println(mPm.getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
+                            UserHandle.USER_SYSTEM));
+                }
+            } else {
+                pw.println();
+                pw.println("No Domain Verifier available!");
+            }
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_LIBS)
+                && packageName == null) {
+            mPm.dumpComputer(DumpState.DUMP_LIBS, fd, pw, dumpState);
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_FEATURES)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            if (!checkin) {
+                pw.println("Features:");
+            }
+
+            synchronized (mPm.mAvailableFeatures) {
+                for (FeatureInfo feat : mPm.mAvailableFeatures.values()) {
+                    if (!checkin) {
+                        pw.print("  ");
+                        pw.print(feat.name);
+                        if (feat.version > 0) {
+                            pw.print(" version=");
+                            pw.print(feat.version);
+                        }
+                        pw.println();
+                    } else {
+                        pw.print("feat,");
+                        pw.print(feat.name);
+                        pw.print(",");
+                        pw.println(feat.version);
+                    }
+                }
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
+            }
+        }
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
+            }
+        }
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
+            }
+        }
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+            mPm.dumpComputer(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)
+                && packageName == null) {
+            mPm.dumpComputer(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+            mPm.dumpComputer(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+            synchronized (mPm.mLock) {
+                mPm.mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
+            synchronized (mPm.mLock) {
+                mPm.mSettings.getKeySetManagerService().dumpLPr(pw, packageName, dumpState);
+            }
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
+            // This cannot be moved to ComputerEngine since some variables of the collections
+            // in PackageUserState such as suspendParams, disabledComponents and enabledComponents
+            // do not have a copy.
+            synchronized (mPm.mLock) {
+                mPm.mSettings.dumpPackagesLPr(pw, packageName, permissionNames, dumpState, checkin);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_QUERIES)) {
+            mPm.dumpComputer(DumpState.DUMP_QUERIES, fd, pw, dumpState);
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
+            // This cannot be moved to ComputerEngine since the set of packages in the
+            // SharedUserSetting do not have a copy.
+            synchronized (mPm.mLock) {
+                mPm.mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState,
+                        checkin);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_CHANGES)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            pw.println("Package Changes:");
+            synchronized (mPm.mLock) {
+                pw.print("  Sequence number="); pw.println(mPm.mChangedPackagesSequenceNumber);
+                final int numChangedPackages = mPm.mChangedPackages.size();
+                for (int i = 0; i < numChangedPackages; i++) {
+                    final SparseArray<String> changes = mPm.mChangedPackages.valueAt(i);
+                    pw.print("  User "); pw.print(mPm.mChangedPackages.keyAt(i)); pw.println(":");
+                    final int numChanges = changes.size();
+                    if (numChanges == 0) {
+                        pw.print("    "); pw.println("No packages changed");
+                    } else {
+                        for (int j = 0; j < numChanges; j++) {
+                            final String pkgName = changes.valueAt(j);
+                            final int sequenceNumber = changes.keyAt(j);
+                            pw.print("    ");
+                            pw.print("seq=");
+                            pw.print(sequenceNumber);
+                            pw.print(", package=");
+                            pw.println(pkgName);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_FROZEN)
+                && packageName == null) {
+            // XXX should handle packageName != null by dumping only install data that
+            // the given package is involved with.
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+            ipw.println();
+            ipw.println("Frozen packages:");
+            ipw.increaseIndent();
+            synchronized (mPm.mLock) {
+                if (mPm.mFrozenPackages.size() == 0) {
+                    ipw.println("(none)");
+                } else {
+                    for (int i = 0; i < mPm.mFrozenPackages.size(); i++) {
+                        ipw.println(mPm.mFrozenPackages.valueAt(i));
+                    }
+                }
+            }
+            ipw.decreaseIndent();
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_VOLUMES)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+            ipw.println();
+            ipw.println("Loaded volumes:");
+            ipw.increaseIndent();
+            synchronized (mPm.mLoadedVolumes) {
+                if (mPm.mLoadedVolumes.size() == 0) {
+                    ipw.println("(none)");
+                } else {
+                    for (int i = 0; i < mPm.mLoadedVolumes.size(); i++) {
+                        ipw.println(mPm.mLoadedVolumes.valueAt(i));
+                    }
+                }
+            }
+            ipw.decreaseIndent();
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
+                && packageName == null) {
+            synchronized (mPm.mLock) {
+                mPm.mComponentResolver.dumpServicePermissions(pw, dumpState);
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_DEXOPT)) {
+            mPm.dumpComputer(DumpState.DUMP_DEXOPT, fd, pw, dumpState);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) {
+            mPm.dumpComputer(DumpState.DUMP_COMPILER_STATS, fd, pw, dumpState);
+        }
+
+        if (dumpState.isDumping(DumpState.DUMP_MESSAGES)
+                && packageName == null) {
+            if (!checkin) {
+                if (dumpState.onTitlePrinted()) {
+                    pw.println();
+                }
+                synchronized (mPm.mLock) {
+                    mPm.mSettings.dumpReadMessagesLPr(pw, dumpState);
+                }
+                pw.println();
+                pw.println("Package warning messages:");
+                dumpCriticalInfo(pw, null);
+            } else {
+                dumpCriticalInfo(pw, "msg,");
+            }
+        }
+
+        // PackageInstaller should be called outside of mPackages lock
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_INSTALLS)
+                && packageName == null) {
+            // XXX should handle packageName != null by dumping only install data that
+            // the given package is involved with.
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            mPm.mInstallerService.dump(new IndentingPrintWriter(pw, "  ", 120));
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_APEX)
+                && (packageName == null || mPm.mApexManager.isApexPackage(packageName))) {
+            mPm.mApexManager.dump(pw, packageName);
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            pw.println("Per UID read timeouts:");
+            pw.println("    Default timeouts flag: " + PackageManagerService.getDefaultTimeouts());
+            pw.println("    Known digesters list flag: "
+                    + PackageManagerService.getKnownDigestersList());
+
+            PerUidReadTimeouts[] items = mPm.getPerUidReadTimeouts();
+            pw.println("    Timeouts (" + items.length + "):");
+            for (PerUidReadTimeouts item : items) {
+                pw.print("        (");
+                pw.print("uid=" + item.uid + ", ");
+                pw.print("minTimeUs=" + item.minTimeUs + ", ");
+                pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", ");
+                pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs);
+                pw.println(")");
+            }
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            pw.println("Snapshot statistics");
+            mPm.dumpSnapshotStats(pw, dumpState.isBrief());
+        }
+
+        if (!checkin
+                && dumpState.isDumping(DumpState.DUMP_PROTECTED_BROADCASTS)
+                && packageName == null) {
+            if (dumpState.onTitlePrinted()) {
+                pw.println();
+            }
+            pw.println("Protected broadcast actions:");
+            synchronized (mPm.mProtectedBroadcasts) {
+                for (int i = 0; i < mPm.mProtectedBroadcasts.size(); i++) {
+                    pw.print("  ");
+                    pw.println(mPm.mProtectedBroadcasts.valueAt(i));
+                }
+            }
+
+        }
+    }
+
+    private void printHelp(PrintWriter pw) {
+        pw.println("Package manager dump options:");
+        pw.println("  [-h] [-f] [--checkin] [--all-components] [cmd] ...");
+        pw.println("    --checkin: dump for a checkin");
+        pw.println("    -f: print details of intent filters");
+        pw.println("    -h: print this help");
+        pw.println("    --all-components: include all component names in package dump");
+        pw.println("  cmd may be one of:");
+        pw.println("    apex: list active APEXes and APEX session state");
+        pw.println("    l[ibraries]: list known shared libraries");
+        pw.println("    f[eatures]: list device features");
+        pw.println("    k[eysets]: print known keysets");
+        pw.println("    r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
+        pw.println("    perm[issions]: dump permissions");
+        pw.println("    permission [name ...]: dump declaration and use of given permission");
+        pw.println("    pref[erred]: print preferred package settings");
+        pw.println("    preferred-xml [--full]: print preferred package settings as xml");
+        pw.println("    prov[iders]: dump content providers");
+        pw.println("    p[ackages]: dump installed packages");
+        pw.println("    q[ueries]: dump app queryability calculations");
+        pw.println("    s[hared-users]: dump shared user IDs");
+        pw.println("    m[essages]: print collected runtime messages");
+        pw.println("    v[erifiers]: print package verifier info");
+        pw.println("    d[omain-preferred-apps]: print domains preferred apps");
+        pw.println("    i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
+        pw.println("    t[imeouts]: print read timeouts for known digesters");
+        pw.println("    version: print database version info");
+        pw.println("    write: write current settings now");
+        pw.println("    installs: details about install sessions");
+        pw.println("    check-permission <permission> <package> [<user>]: does pkg hold perm?");
+        pw.println("    dexopt: dump dexopt state");
+        pw.println("    compiler-stats: dump compiler statistics");
+        pw.println("    service-permissions: dump permissions required by services");
+        pw.println("    snapshot: dump snapshot statistics");
+        pw.println("    protected-broadcasts: print list of protected broadcast actions");
+        pw.println("    known-packages: dump known packages");
+        pw.println("    <package.name>: info about given package");
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        synchronized (mPm.mLock) {
+            final long requiredVerifierPackageToken =
+                    proto.start(PackageServiceDumpProto.REQUIRED_VERIFIER_PACKAGE);
+            proto.write(PackageServiceDumpProto.PackageShortProto.NAME,
+                    mPm.mRequiredVerifierPackage);
+            proto.write(
+                    PackageServiceDumpProto.PackageShortProto.UID,
+                    mPm.getPackageUid(
+                            mPm.mRequiredVerifierPackage,
+                            MATCH_DEBUG_TRIAGED_MISSING,
+                            UserHandle.USER_SYSTEM));
+            proto.end(requiredVerifierPackageToken);
+
+            DomainVerificationProxy proxy = mPm.mDomainVerificationManager.getProxy();
+            ComponentName verifierComponent = proxy.getComponentName();
+            if (verifierComponent != null) {
+                String verifierPackageName = verifierComponent.getPackageName();
+                final long verifierPackageToken =
+                        proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
+                proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
+                proto.write(
+                        PackageServiceDumpProto.PackageShortProto.UID,
+                        mPm.getPackageUid(
+                                verifierPackageName,
+                                MATCH_DEBUG_TRIAGED_MISSING,
+                                UserHandle.USER_SYSTEM));
+                proto.end(verifierPackageToken);
+            }
+
+            dumpSharedLibrariesProto(proto);
+            dumpFeaturesProto(proto);
+            mPm.mSettings.dumpPackagesProto(proto);
+            mPm.mSettings.dumpSharedUsersProto(proto);
+            dumpCriticalInfo(proto);
+        }
+        proto.flush();
+    }
+
+    private void dumpFeaturesProto(ProtoOutputStream proto) {
+        synchronized (mPm.mAvailableFeatures) {
+            final int count = mPm.mAvailableFeatures.size();
+            for (int i = 0; i < count; i++) {
+                mPm.mAvailableFeatures.valueAt(i).dumpDebug(proto,
+                        PackageServiceDumpProto.FEATURES);
+            }
+        }
+    }
+
+    private void dumpSharedLibrariesProto(ProtoOutputStream proto) {
+        final int count = mPm.mSharedLibraries.size();
+        for (int i = 0; i < count; i++) {
+            final String libName = mPm.mSharedLibraries.keyAt(i);
+            WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
+                    mPm.mSharedLibraries.get(libName);
+            if (versionedLib == null) {
+                continue;
+            }
+            final int versionCount = versionedLib.size();
+            for (int j = 0; j < versionCount; j++) {
+                final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j);
+                final long sharedLibraryToken =
+                        proto.start(PackageServiceDumpProto.SHARED_LIBRARIES);
+                proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName());
+                final boolean isJar = (libraryInfo.getPath() != null);
+                proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar);
+                if (isJar) {
+                    proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH,
+                            libraryInfo.getPath());
+                } else {
+                    proto.write(PackageServiceDumpProto.SharedLibraryProto.APK,
+                            libraryInfo.getPackageName());
+                }
+                proto.end(sharedLibraryToken);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/FileInstallArgs.java b/services/core/java/com/android/server/pm/FileInstallArgs.java
index cb174eb..02c8c12 100644
--- a/services/core/java/com/android/server/pm/FileInstallArgs.java
+++ b/services/core/java/com/android/server/pm/FileInstallArgs.java
@@ -71,7 +71,10 @@
         super(params);
     }
 
-    /** Existing install */
+    /**
+     * Create args that describe an existing installed package. Typically used
+     * when cleaning up old installs, or used as a move source.
+     */
     FileInstallArgs(String codePath, String[] instructionSets, PackageManagerService pm) {
         super(OriginInfo.fromNothing(), null, null, 0, InstallSource.EMPTY,
                 null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0,
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
new file mode 100644
index 0000000..80dd6ef
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -0,0 +1,952 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
+import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.POST_INSTALL;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.backup.IBackupManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.DataLoaderType;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.content.pm.pkg.PackageUserState;
+import android.os.Binder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.rollback.RollbackManagerInternal;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+final class InstallPackageHelper {
+    private final PackageManagerService mPm;
+    private final AppDataHelper mAppDataHelper;
+    private final PackageManagerServiceInjector mInjector;
+
+    // TODO(b/198166813): remove PMS dependency
+    InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
+        mPm = pm;
+        mInjector = pm.mInjector;
+        mAppDataHelper = appDataHelper;
+    }
+
+    InstallPackageHelper(PackageManagerService pm) {
+        this(pm, new AppDataHelper(pm));
+    }
+
+    InstallPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+        mPm = pm;
+        mInjector = injector;
+        mAppDataHelper = new AppDataHelper(pm, mInjector);
+    }
+
+    @GuardedBy("mPm.mLock")
+    public Map<String, ReconciledPackage> reconcilePackagesLocked(
+            final ReconcileRequest request, KeySetManagerService ksms,
+            PackageManagerServiceInjector injector)
+            throws ReconcileFailure {
+        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
+
+        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+
+        // make a copy of the existing set of packages so we can combine them with incoming packages
+        final ArrayMap<String, AndroidPackage> combinedPackages =
+                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+
+        combinedPackages.putAll(request.mAllPackages);
+
+        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
+                new ArrayMap<>();
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+
+            // add / replace existing with incoming packages
+            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
+                    scanResult.mRequest.mParsedPackage);
+
+            // in the first pass, we'll build up the set of incoming shared libraries
+            final List<SharedLibraryInfo> allowedSharedLibInfos =
+                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
+                            request.mSharedLibrarySource);
+            final SharedLibraryInfo staticLib = scanResult.mStaticSharedLibraryInfo;
+            if (allowedSharedLibInfos != null) {
+                for (SharedLibraryInfo info : allowedSharedLibInfos) {
+                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
+                            incomingSharedLibraries, info)) {
+                        throw new ReconcileFailure("Static Shared Library " + staticLib.getName()
+                                + " is being installed twice in this set!");
+                    }
+                }
+            }
+
+            // the following may be null if we're just reconciling on boot (and not during install)
+            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
+            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
+            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
+            final boolean isInstall = installArgs != null;
+            if (isInstall && (res == null || prepareResult == null)) {
+                throw new ReconcileFailure("Reconcile arguments are not balanced for "
+                        + installPackageName + "!");
+            }
+
+            final DeletePackageAction deletePackageAction;
+            // we only want to try to delete for non system apps
+            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
+                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
+                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
+                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+                        deleteFlags, null /* all users */);
+                if (deletePackageAction == null) {
+                    throw new ReconcileFailure(
+                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+                            "May not delete " + installPackageName + " to replace");
+                }
+            } else {
+                deletePackageAction = null;
+            }
+
+            final int scanFlags = scanResult.mRequest.mScanFlags;
+            final int parseFlags = scanResult.mRequest.mParseFlags;
+            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+
+            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+            final PackageSetting lastStaticSharedLibSetting =
+                    request.mLastStaticSharedLibSettings.get(installPackageName);
+            final PackageSetting signatureCheckPs =
+                    (prepareResult != null && lastStaticSharedLibSetting != null)
+                            ? lastStaticSharedLibSetting
+                            : scanResult.mPkgSetting;
+            boolean removeAppKeySetData = false;
+            boolean sharedUserSignaturesChanged = false;
+            SigningDetails signingDetails = null;
+            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                } else {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " upgrade keys do not match the previously installed"
+                                        + " version");
+                    } else {
+                        String msg = "System package " + parsedPackage.getPackageName()
+                                + " signature changed; retaining data.";
+                        PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    }
+                }
+                signingDetails = parsedPackage.getSigningDetails();
+            } else {
+                try {
+                    final Settings.VersionInfo versionInfo =
+                            request.mVersionInfos.get(installPackageName);
+                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
+                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+                    final boolean isRollback = installArgs != null
+                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+                    final boolean compatMatch = verifySignatures(signatureCheckPs,
+                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
+                            compareRecover, isRollback);
+                    // The new KeySets will be re-added later in the scanning process.
+                    if (compatMatch) {
+                        removeAppKeySetData = true;
+                    }
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // if this is is a sharedUser, check to see if the new package is signed by a
+                    // newer
+                    // signing certificate than the existing one, and if so, copy over the new
+                    // details
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        // Attempt to merge the existing lineage for the shared SigningDetails with
+                        // the lineage of the new package; if the shared SigningDetails are not
+                        // returned this indicates the new package added new signers to the lineage
+                        // and/or changed the capabilities of existing signers in the lineage.
+                        SigningDetails sharedSigningDetails =
+                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
+                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
+                                signingDetails);
+                        if (mergedDetails != sharedSigningDetails) {
+                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                    mergedDetails;
+                        }
+                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
+                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
+                        }
+                    }
+                } catch (PackageManagerException e) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(e);
+                    }
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // If the system app is part of a shared user we allow that shared user to
+                    // change
+                    // signatures as well as part of an OTA. We still need to verify that the
+                    // signatures
+                    // are consistent within the shared user for a given boot, so only allow
+                    // updating
+                    // the signatures on the first package scanned for the shared user (i.e. if the
+                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
+                                .signatures.mSigningDetails.getSignatures();
+                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
+                                && compareSignatures(sharedUserSignatures,
+                                parsedPackage.getSigningDetails().getSignatures())
+                                != PackageManager.SIGNATURE_MATCH) {
+                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
+                                // Mismatched signatures is an error and silently skipping system
+                                // packages will likely break the device in unforeseen ways.
+                                // However, we allow the device to boot anyway because, prior to Q,
+                                // vendors were not expecting the platform to crash in this
+                                // situation.
+                                // This WILL be a hard failure on any new API levels after Q.
+                                throw new ReconcileFailure(
+                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                        "Signature mismatch for shared user: "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            } else {
+                                // Treat mismatched signatures on system packages using a shared
+                                // UID as
+                                // fatal for the system overall, rather than just failing to install
+                                // whichever package happened to be scanned later.
+                                throw new IllegalStateException(
+                                        "Signature mismatch on system package "
+                                                + parsedPackage.getPackageName()
+                                                + " for shared user "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            }
+                        }
+
+                        sharedUserSignaturesChanged = true;
+                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                parsedPackage.getSigningDetails();
+                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
+                    }
+                    // File a report about this.
+                    String msg = "System package " + parsedPackage.getPackageName()
+                            + " signature changed; retaining data.";
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                } catch (IllegalArgumentException e) {
+                    // should never happen: certs matched when checking, but not when comparing
+                    // old to new for sharedUser
+                    throw new RuntimeException(
+                            "Signing certificates comparison made on incomparable signing details"
+                                    + " but somehow passed verifySignatures!", e);
+                }
+            }
+
+            result.put(installPackageName,
+                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
+                            res, request.mPreparedPackages.get(installPackageName), scanResult,
+                            deletePackageAction, allowedSharedLibInfos, signingDetails,
+                            sharedUserSignaturesChanged, removeAppKeySetData));
+        }
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            // Check all shared libraries and map to their actual file path.
+            // We only do this here for apps not on a system dir, because those
+            // are the only ones that can fail an install due to this.  We
+            // will take care of the system apps by updating all of their
+            // library paths after the scan is done. Also during the initial
+            // scan don't update any libs as we do this wholesale after all
+            // apps are scanned to avoid dependency based scanning.
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
+                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    != 0) {
+                continue;
+            }
+            try {
+                result.get(installPackageName).mCollectedSharedLibraryInfos =
+                        SharedLibraryHelper.collectSharedLibraryInfos(
+                                scanResult.mRequest.mParsedPackage,
+                                combinedPackages, request.mSharedLibrarySource,
+                                incomingSharedLibraries, injector.getCompatibility());
+
+            } catch (PackageManagerException e) {
+                throw new ReconcileFailure(e.error, e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Commits the package scan and modifies system state.
+     * <p><em>WARNING:</em> The method may throw an exception in the middle
+     * of committing the package, leaving the system in an inconsistent state.
+     * This needs to be fixed so, once we get to this point, no errors are
+     * possible and the system is not left in an inconsistent state.
+     */
+    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
+    public AndroidPackage commitReconciledScanResultLocked(
+            @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
+        final ScanResult result = reconciledPkg.mScanResult;
+        final ScanRequest request = result.mRequest;
+        // TODO(b/135203078): Move this even further away
+        ParsedPackage parsedPackage = request.mParsedPackage;
+        if ("android".equals(parsedPackage.getPackageName())) {
+            // TODO(b/135203078): Move this to initial parse
+            parsedPackage.setVersionCode(mPm.getSdkVersion())
+                    .setVersionCodeMajor(0);
+        }
+
+        final AndroidPackage oldPkg = request.mOldPkg;
+        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
+        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
+        final PackageSetting oldPkgSetting = request.mOldPkgSetting;
+        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
+        final UserHandle user = request.mUser;
+        final String realPkgName = request.mRealPkgName;
+        final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
+        final PackageSetting pkgSetting;
+        if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null
+                && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) {
+            // shared user changed, remove from old shared user
+            request.mPkgSetting.getSharedUser().removePackage(request.mPkgSetting);
+        }
+        if (result.mExistingSettingCopied) {
+            pkgSetting = request.mPkgSetting;
+            pkgSetting.updateFrom(result.mPkgSetting);
+        } else {
+            pkgSetting = result.mPkgSetting;
+            if (originalPkgSetting != null) {
+                mPm.mSettings.addRenamedPackageLPw(
+                        AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
+                        originalPkgSetting.getPackageName());
+                mPm.mTransferredPackages.add(originalPkgSetting.getPackageName());
+            } else {
+                mPm.mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
+            }
+        }
+        if (pkgSetting.getSharedUser() != null) {
+            pkgSetting.getSharedUser().addPackage(pkgSetting);
+        }
+        if (reconciledPkg.mInstallArgs != null
+                && reconciledPkg.mInstallArgs.mForceQueryableOverride) {
+            pkgSetting.setForceQueryableOverride(true);
+        }
+
+        // If this is part of a standard install, set the initiating package name, else rely on
+        // previous device state.
+        if (reconciledPkg.mInstallArgs != null) {
+            InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource;
+            if (installSource.initiatingPackageName != null) {
+                final PackageSetting ips = mPm.mSettings.getPackageLPr(
+                        installSource.initiatingPackageName);
+                if (ips != null) {
+                    installSource = installSource.setInitiatingPackageSignatures(
+                            ips.getSignatures());
+                }
+            }
+            pkgSetting.setInstallSource(installSource);
+        }
+
+        // TODO(toddke): Consider a method specifically for modifying the Package object
+        // post scan; or, moving this stuff out of the Package object since it has nothing
+        // to do with the package on disk.
+        // We need to have this here because addUserToSettingLPw() is sometimes responsible
+        // for creating the application ID. If we did this earlier, we would be saving the
+        // correct ID.
+        parsedPackage.setUid(pkgSetting.getAppId());
+        final AndroidPackage pkg = parsedPackage.hideAsFinal();
+
+        mPm.mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
+
+        if (realPkgName != null) {
+            mPm.mTransferredPackages.add(pkg.getPackageName());
+        }
+
+        if (reconciledPkg.mCollectedSharedLibraryInfos != null) {
+            mPm.executeSharedLibrariesUpdateLPr(pkg, pkgSetting, null, null,
+                    reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
+        }
+
+        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+        if (reconciledPkg.mRemoveAppKeySetData) {
+            ksms.removeAppKeySetDataLPw(pkg.getPackageName());
+        }
+        if (reconciledPkg.mSharedUserSignaturesChanged) {
+            pkgSetting.getSharedUser().signaturesChanged = Boolean.TRUE;
+            pkgSetting.getSharedUser().signatures.mSigningDetails = reconciledPkg.mSigningDetails;
+        }
+        pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
+
+        if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
+            for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
+                final String codePathString = changedAbiCodePath.get(i);
+                try {
+                    mPm.mInstaller.rmdex(codePathString,
+                            getDexCodeInstructionSet(getPreferredInstructionSet()));
+                } catch (Installer.InstallerException ignored) {
+                }
+            }
+        }
+
+        final int userId = user == null ? 0 : user.getIdentifier();
+        // Modify state for the given package setting
+        commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
+                (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+        if (pkgSetting.getInstantApp(userId)) {
+            mPm.mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
+        }
+        pkgSetting.setStatesOnCommit();
+
+        return pkg;
+    }
+
+    /**
+     * Adds a scanned package to the system. When this method is finished, the package will
+     * be available for query, resolution, etc...
+     */
+    private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+            @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
+            final @PackageManagerService.ScanFlags int scanFlags, boolean chatty,
+            ReconciledPackage reconciledPkg) {
+        final String pkgName = pkg.getPackageName();
+        if (mPm.mCustomResolverComponentName != null
+                && mPm.mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
+            setUpCustomResolverActivity(pkg, pkgSetting);
+        }
+
+        if (pkg.getPackageName().equals("android")) {
+            synchronized (mPm.mLock) {
+                // Set up information for our fall-back user intent resolution activity.
+                mPm.mPlatformPackage = pkg;
+
+                // The instance stored in PackageManagerService is special cased to be non-user
+                // specific, so initialize all the needed fields here.
+                mPm.mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
+                        PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+
+                if (!mPm.mResolverReplaced) {
+                    mPm.mResolveActivity.applicationInfo = mPm.mAndroidApplication;
+                    mPm.mResolveActivity.name = ResolverActivity.class.getName();
+                    mPm.mResolveActivity.packageName = mPm.mAndroidApplication.packageName;
+                    mPm.mResolveActivity.processName = "system:ui";
+                    mPm.mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+                    mPm.mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
+                    mPm.mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+                    mPm.mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
+                    mPm.mResolveActivity.exported = true;
+                    mPm.mResolveActivity.enabled = true;
+                    mPm.mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+                    mPm.mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
+                            | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
+                            | ActivityInfo.CONFIG_SCREEN_LAYOUT
+                            | ActivityInfo.CONFIG_ORIENTATION
+                            | ActivityInfo.CONFIG_KEYBOARD
+                            | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+                    mPm.mResolveInfo.activityInfo = mPm.mResolveActivity;
+                    mPm.mResolveInfo.priority = 0;
+                    mPm.mResolveInfo.preferredOrder = 0;
+                    mPm.mResolveInfo.match = 0;
+                    mPm.mResolveComponentName = new ComponentName(
+                            mPm.mAndroidApplication.packageName, mPm.mResolveActivity.name);
+                }
+                PackageManagerService.onChanged();
+            }
+        }
+
+        ArrayList<AndroidPackage> clientLibPkgs = null;
+        // writer
+        synchronized (mPm.mLock) {
+            if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) {
+                for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) {
+                    mPm.commitSharedLibraryInfoLocked(info);
+                }
+                final Map<String, AndroidPackage> combinedSigningDetails =
+                        reconciledPkg.getCombinedAvailablePackages();
+                try {
+                    // Shared libraries for the package need to be updated.
+                    mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
+                            combinedSigningDetails);
+                } catch (PackageManagerException e) {
+                    Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e);
+                }
+                // Update all applications that use this library. Skip when booting
+                // since this will be done after all packages are scaned.
+                if ((scanFlags & SCAN_BOOTING) == 0) {
+                    clientLibPkgs = mPm.updateAllSharedLibrariesLocked(pkg, pkgSetting,
+                            combinedSigningDetails);
+                }
+            }
+        }
+        if (reconciledPkg.mInstallResult != null) {
+            reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs;
+        }
+
+        if ((scanFlags & SCAN_BOOTING) != 0) {
+            // No apps can run during boot scan, so they don't need to be frozen
+        } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {
+            // Caller asked to not kill app, so it's probably not frozen
+        } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {
+            // Caller asked us to ignore frozen check for some reason; they
+            // probably didn't know the package name
+        } else {
+            // We're doing major surgery on this package, so it better be frozen
+            // right now to keep it from launching
+            mPm.checkPackageFrozen(pkgName);
+        }
+
+        // Also need to kill any apps that are dependent on the library.
+        if (clientLibPkgs != null) {
+            for (int i = 0; i < clientLibPkgs.size(); i++) {
+                AndroidPackage clientPkg = clientLibPkgs.get(i);
+                mPm.killApplication(clientPkg.getPackageName(),
+                        clientPkg.getUid(), "update lib");
+            }
+        }
+
+        // writer
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
+
+        synchronized (mPm.mLock) {
+            // We don't expect installation to fail beyond this point
+            // Add the new setting to mSettings
+            mPm.mSettings.insertPackageSettingLPw(pkgSetting, pkg);
+            // Add the new setting to mPackages
+            mPm.mPackages.put(pkg.getPackageName(), pkg);
+            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+                mPm.mApexManager.registerApkInApex(pkg);
+            }
+
+            // Add the package's KeySets to the global KeySetManagerService
+            KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+            ksms.addScannedPackageLPw(pkg);
+
+            mPm.mComponentResolver.addAllComponents(pkg, chatty);
+            final boolean isReplace =
+                    reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
+            mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
+            mPm.addAllPackageProperties(pkg);
+
+            if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
+                mPm.mDomainVerificationManager.addPackage(pkgSetting);
+            } else {
+                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+            }
+
+            int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
+            StringBuilder r = null;
+            int i;
+            for (i = 0; i < collectionSize; i++) {
+                ParsedInstrumentation a = pkg.getInstrumentations().get(i);
+                a.setPackageName(pkg.getPackageName());
+                mPm.addInstrumentation(a.getComponentName(), a);
+                if (chatty) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(a.getName());
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Instrumentation: " + r);
+            }
+
+            final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
+            if (!protectedBroadcasts.isEmpty()) {
+                synchronized (mPm.mProtectedBroadcasts) {
+                    mPm.mProtectedBroadcasts.addAll(protectedBroadcasts);
+                }
+            }
+
+            mPm.mPermissionManager.onPackageAdded(pkg,
+                    (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
+        }
+
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    private void setUpCustomResolverActivity(AndroidPackage pkg, PackageSetting pkgSetting) {
+        synchronized (mPm.mLock) {
+            mPm.mResolverReplaced = true;
+
+            // The instance created in PackageManagerService is special cased to be non-user
+            // specific, so initialize all the needed fields here.
+            ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
+                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+
+            // Set up information for custom user intent resolution activity.
+            mPm.mResolveActivity.applicationInfo = appInfo;
+            mPm.mResolveActivity.name = mPm.mCustomResolverComponentName.getClassName();
+            mPm.mResolveActivity.packageName = pkg.getPackageName();
+            mPm.mResolveActivity.processName = pkg.getProcessName();
+            mPm.mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+            mPm.mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+                    | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+            mPm.mResolveActivity.theme = 0;
+            mPm.mResolveActivity.exported = true;
+            mPm.mResolveActivity.enabled = true;
+            mPm.mResolveInfo.activityInfo = mPm.mResolveActivity;
+            mPm.mResolveInfo.priority = 0;
+            mPm.mResolveInfo.preferredOrder = 0;
+            mPm.mResolveInfo.match = 0;
+            mPm.mResolveComponentName = mPm.mCustomResolverComponentName;
+            PackageManagerService.onChanged();
+            Slog.i(TAG, "Replacing default ResolverActivity with custom activity: "
+                    + mPm.mResolveComponentName);
+        }
+    }
+
+    /**
+     * If the database version for this type of package (internal storage or
+     * external storage) is less than the version where package signatures
+     * were updated, return true.
+     */
+    public boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
+        return isCompatSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
+    }
+
+    public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
+    }
+
+    public boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
+        return isRecoverSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
+    }
+
+    public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
+    }
+
+    public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
+            @PackageManager.InstallFlags int installFlags,
+            @PackageManager.InstallReason int installReason,
+            @Nullable List<String> allowlistedRestrictedPermissions,
+            @Nullable IntentSender intentSender) {
+        if (DEBUG_INSTALL) {
+            Log.v(TAG, "installExistingPackageAsUser package=" + packageName + " userId=" + userId
+                    + " installFlags=" + installFlags + " installReason=" + installReason
+                    + " allowlistedRestrictedPermissions=" + allowlistedRestrictedPermissions);
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPm.mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED
+                && mPm.mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Neither user " + callingUid + " nor current process has "
+                    + android.Manifest.permission.INSTALL_PACKAGES + ".");
+        }
+        PackageSetting pkgSetting;
+        mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+                true /* checkShell */, "installExistingPackage for user " + userId);
+        if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
+            return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+        }
+
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean installed = false;
+            final boolean instantApp =
+                    (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+            final boolean fullApp =
+                    (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+
+            // writer
+            synchronized (mPm.mLock) {
+                pkgSetting = mPm.mSettings.getPackageLPr(packageName);
+                if (pkgSetting == null) {
+                    return PackageManager.INSTALL_FAILED_INVALID_URI;
+                }
+                if (!mPm.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
+                    // only allow the existing package to be used if it's installed as a full
+                    // application for at least one user
+                    boolean installAllowed = false;
+                    for (int checkUserId : mPm.mUserManager.getUserIds()) {
+                        installAllowed = !pkgSetting.getInstantApp(checkUserId);
+                        if (installAllowed) {
+                            break;
+                        }
+                    }
+                    if (!installAllowed) {
+                        return PackageManager.INSTALL_FAILED_INVALID_URI;
+                    }
+                }
+                if (!pkgSetting.getInstalled(userId)) {
+                    pkgSetting.setInstalled(true, userId);
+                    pkgSetting.setHidden(false, userId);
+                    pkgSetting.setInstallReason(installReason, userId);
+                    pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
+                    mPm.mSettings.writePackageRestrictionsLPr(userId);
+                    mPm.mSettings.writeKernelMappingLPr(pkgSetting);
+                    installed = true;
+                } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+                    // upgrade app from instant to full; we don't allow app downgrade
+                    installed = true;
+                }
+                mPm.setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
+            }
+
+            if (installed) {
+                if (pkgSetting.getPkg() != null) {
+                    final PermissionManagerServiceInternal.PackageInstalledParams.Builder
+                            permissionParamsBuilder =
+                            new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
+                    if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
+                            != 0) {
+                        permissionParamsBuilder.setAllowlistedRestrictedPermissions(
+                                pkgSetting.getPkg().getRequestedPermissions());
+                    }
+                    mPm.mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
+                            Process.INVALID_UID /* previousAppId */,
+                            permissionParamsBuilder.build(), userId);
+                }
+
+                if (pkgSetting.getPkg() != null) {
+                    synchronized (mPm.mInstallLock) {
+                        // We don't need to freeze for a brand new install
+                        mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg());
+                    }
+                }
+                mPm.sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
+                synchronized (mPm.mLock) {
+                    mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId });
+                }
+                // start async restore with no post-install since we finish install here
+                PackageInstalledInfo res = new PackageInstalledInfo(
+                        PackageManager.INSTALL_SUCCEEDED);
+                res.mPkg = pkgSetting.getPkg();
+                res.mNewUsers = new int[]{ userId };
+
+                PostInstallData postInstallData =
+                        new PostInstallData(null, res, () -> {
+                            mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
+                                    userId);
+                            if (intentSender != null) {
+                                onRestoreComplete(res.mReturnCode, mPm.mContext, intentSender);
+                            }
+                        });
+                restoreAndPostInstall(userId, res, postInstallData);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+
+        return PackageManager.INSTALL_SUCCEEDED;
+    }
+
+    private static void onRestoreComplete(int returnCode, Context context, IntentSender target) {
+        Intent fillIn = new Intent();
+        fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
+                PackageManager.installStatusToPublicStatus(returnCode));
+        try {
+            target.sendIntent(context, 0, fillIn, null, null);
+        } catch (IntentSender.SendIntentException ignored) {
+        }
+    }
+
+    /** @param data Post-install is performed only if this is non-null. */
+    public void restoreAndPostInstall(
+            int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
+        if (DEBUG_INSTALL) {
+            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg);
+        }
+
+        // A restore should be requested at this point if (a) the install
+        // succeeded, (b) the operation is not an update.
+        final boolean update = res.mRemovedInfo != null
+                && res.mRemovedInfo.mRemovedPackage != null;
+        boolean doRestore = !update && res.mPkg != null;
+
+        // Set up the post-install work request bookkeeping.  This will be used
+        // and cleaned up by the post-install event handling regardless of whether
+        // there's a restore pass performed.  Token values are >= 1.
+        int token;
+        if (mPm.mNextInstallToken < 0) mPm.mNextInstallToken = 1;
+        token = mPm.mNextInstallToken++;
+        if (data != null) {
+            mPm.mRunningInstalls.put(token, data);
+        } else if (DEBUG_INSTALL) {
+            Log.v(TAG, "No post-install required for " + token);
+        }
+
+        if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+            // Pass responsibility to the Backup Manager.  It will perform a
+            // restore if appropriate, then pass responsibility back to the
+            // Package Manager to run the post-install observer callbacks
+            // and broadcasts.
+            if (res.mFreezer != null) {
+                res.mFreezer.close();
+            }
+            doRestore = performBackupManagerRestore(userId, token, res);
+        }
+
+        // If this is an update to a package that might be potentially downgraded, then we
+        // need to check with the rollback manager whether there's any userdata that might
+        // need to be snapshotted or restored for the package.
+        //
+        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
+        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
+            doRestore = performRollbackManagerRestore(userId, token, res, data);
+        }
+
+        if (!doRestore) {
+            // No restore possible, or the Backup Manager was mysteriously not
+            // available -- just fire the post-install work request directly.
+            if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+
+            Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
+
+            Message msg = mPm.mHandler.obtainMessage(POST_INSTALL, token, 0);
+            mPm.mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
+     * Returns whether the restore successfully completed.
+     */
+    private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
+        IBackupManager bm = IBackupManager.Stub.asInterface(
+                ServiceManager.getService(Context.BACKUP_SERVICE));
+        if (bm != null) {
+            // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
+            // in the BackupManager. USER_ALL is used in compatibility tests.
+            if (userId == UserHandle.USER_ALL) {
+                userId = UserHandle.USER_SYSTEM;
+            }
+            if (DEBUG_INSTALL) {
+                Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
+            }
+            Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
+            try {
+                if (bm.isUserReadyForBackup(userId)) {
+                    bm.restoreAtInstallForUser(
+                            userId, res.mPkg.getPackageName(), token);
+                } else {
+                    Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
+                            + "didn't take place.");
+                    return false;
+                }
+            } catch (RemoteException e) {
+                // can't happen; the backup manager is local
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception trying to enqueue restore", e);
+                return false;
+            }
+        } else {
+            Slog.e(TAG, "Backup Manager not found!");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
+     * Returns whether the restore successfully completed.
+     */
+    private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
+            PostInstallData data) {
+        RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
+
+        final String packageName = res.mPkg.getPackageName();
+        final int[] allUsers = mPm.mUserManager.getUserIds();
+        final int[] installedUsers;
+
+        final PackageSetting ps;
+        int appId = -1;
+        long ceDataInode = -1;
+        synchronized (mPm.mLock) {
+            ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null) {
+                appId = ps.getAppId();
+                ceDataInode = ps.getCeDataInode(userId);
+            }
+
+            // NOTE: We ignore the user specified in the InstallParam because we know this is
+            // an update, and hence need to restore data for all installed users.
+            installedUsers = ps.queryInstalledUsers(allUsers, true);
+        }
+
+        boolean doSnapshotOrRestore = data != null && data.args != null
+                && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+
+        if (ps != null && doSnapshotOrRestore) {
+            final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
+            rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
+                    appId, ceDataInode, seInfo, token);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index 5a18d40..94065d9 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -163,6 +163,7 @@
     final int mDataLoaderType;
     final long mRequiredInstalledVersionCode;
     final PackageLite mPackageLite;
+    final InstallPackageHelper mInstallPackageHelper;
 
     InstallParams(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
             int installFlags, InstallSource installSource, String volumeUuid,
@@ -187,6 +188,7 @@
         mDataLoaderType = DataLoaderType.NONE;
         mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         mPackageLite = packageLite;
+        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     InstallParams(File stagedDir, IPackageInstallObserver2 observer,
@@ -213,6 +215,7 @@
                 ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
         mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
         mPackageLite = packageLite;
+        mInstallPackageHelper = new InstallPackageHelper(mPm);
     }
 
     @Override
@@ -463,7 +466,7 @@
                 }
             }
             for (InstallRequest request : apkInstallRequests) {
-                mPm.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
+                mInstallPackageHelper.restoreAndPostInstall(request.mArgs.mUser.getIdentifier(),
                         request.mInstallResult,
                         new PostInstallData(request.mArgs,
                                 request.mInstallResult, null));
@@ -624,7 +627,7 @@
                 Map<String, ReconciledPackage> reconciledPackages;
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
-                    reconciledPackages = mPm.reconcilePackagesLocked(
+                    reconciledPackages = mInstallPackageHelper.reconcilePackagesLocked(
                             reconcileRequest, mPm.mSettings.getKeySetManagerService(),
                             mPm.mInjector);
                 } catch (ReconcileFailure e) {
@@ -882,10 +885,10 @@
                     }
                 } else {
                     try {
-                        final boolean compareCompat = mPm.isCompatSignatureUpdateNeeded(
-                                parsedPackage);
-                        final boolean compareRecover = mPm.isRecoverSignatureUpdateNeeded(
-                                parsedPackage);
+                        final boolean compareCompat =
+                                mInstallPackageHelper.isCompatSignatureUpdateNeeded(parsedPackage);
+                        final boolean compareRecover =
+                                mInstallPackageHelper.isRecoverSignatureUpdateNeeded(parsedPackage);
                         // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(signatureCheckPs, null,
                                 parsedPackage.getSigningDetails(), compareCompat, compareRecover,
@@ -1561,13 +1564,13 @@
                         // We didn't need to disable the .apk as a current system package,
                         // which means we are replacing another update that is already
                         // installed.  We need to make sure to delete the older one's .apk.
-                        res.mRemovedInfo.mArgs = mPm.createInstallArgsForExisting(
+                        res.mRemovedInfo.mArgs = new FileInstallArgs(
                                 oldPackage.getPath(),
                                 getAppDexInstructionSets(
                                         AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
                                                 deletedPkgSetting),
                                         AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
-                                                deletedPkgSetting)));
+                                                deletedPkgSetting)), mPm);
                     } else {
                         res.mRemovedInfo.mArgs = null;
                     }
@@ -1627,8 +1630,8 @@
                 }
             }
 
-            AndroidPackage pkg = mPm.commitReconciledScanResultLocked(reconciledPkg,
-                    request.mAllUsers);
+            AndroidPackage pkg = mInstallPackageHelper.commitReconciledScanResultLocked(
+                    reconciledPkg, request.mAllUsers);
             updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, res);
 
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
@@ -1973,7 +1976,7 @@
             // If this is an update of a package which used to fail to compile,
             // BackgroundDexOptService will remove it from its denylist.
             // TODO: Layering violation
-            BackgroundDexOptService.notifyPackageChanged(packageName);
+            BackgroundDexOptService.getService().notifyPackageChanged(packageName);
 
             notifyPackageChangeObserversOnUpdate(reconciledPkg);
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 8721603..375ffc4 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -746,11 +746,15 @@
         @Override
         public Bundle getSuspendedPackageLauncherExtras(String packageName,
                 UserHandle user) {
-            if (!canAccessProfile(user.getIdentifier(), "Cannot get launcher extras")) {
+            final int callingUid = injectBinderCallingUid();
+            final int userId = user.getIdentifier();
+            if (!canAccessProfile(userId, "Cannot get launcher extras")) {
                 return null;
             }
-            return mPackageManagerInternal.getSuspendedPackageLauncherExtras(packageName,
-                    user.getIdentifier());
+            if (mPackageManagerInternal.filterAppAccess(packageName, callingUid, userId)) {
+                return null;
+            }
+            return mPackageManagerInternal.getSuspendedPackageLauncherExtras(packageName, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d89af91..2595bb4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1115,9 +1115,10 @@
 
     @Override
     public void installExistingPackage(String packageName, int installFlags, int installReason,
-            IntentSender statusReceiver, int userId, List<String> whiteListedPermissions) {
-        mPm.installExistingPackageAsUser(packageName, userId, installFlags, installReason,
-                whiteListedPermissions, statusReceiver);
+            IntentSender statusReceiver, int userId, List<String> allowListedPermissions) {
+        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
+        installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+                installReason, allowListedPermissions, statusReceiver);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2bec8be..66320fe 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
 import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -29,10 +28,6 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
-import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 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_DISABLED_COMPONENTS;
@@ -45,7 +40,6 @@
 import static android.content.pm.PackageManager.TYPE_PROVIDER;
 import static android.content.pm.PackageManager.TYPE_RECEIVER;
 import static android.content.pm.PackageManager.TYPE_UNKNOWN;
-import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
@@ -59,9 +53,7 @@
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
-import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 
 import android.Manifest;
 import android.annotation.AppIdInt;
@@ -75,10 +67,8 @@
 import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
-import android.app.PendingIntent;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
-import android.app.backup.IBackupManager;
 import android.app.role.RoleManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -87,7 +77,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -158,7 +147,6 @@
 import android.content.pm.dex.IArtManager;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedMainComponent;
@@ -205,7 +193,6 @@
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.security.KeyStore;
-import android.service.pm.PackageServiceDumpProto;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -215,23 +202,18 @@
 import android.util.ExceptionUtils;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.LogPrinter;
-import android.util.PackageUtils;
 import android.util.Pair;
-import android.util.PrintStreamPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
-import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.content.om.OverlayConfig;
@@ -243,7 +225,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.FunctionalUtils;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.EventLogTags;
@@ -258,9 +239,7 @@
 import com.android.server.apphibernation.AppHibernationService;
 import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
-import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.ArtUtils;
@@ -285,7 +264,6 @@
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
-import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.SnapshotCache;
@@ -303,9 +281,6 @@
 import libcore.util.EmptyArray;
 import libcore.util.HexEncoding;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -315,7 +290,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.Certificate;
@@ -400,7 +374,6 @@
     public static final boolean DEBUG_PACKAGE_SCANNING = false;
     static final boolean DEBUG_VERIFY = false;
     public static final boolean DEBUG_PERMISSIONS = false;
-    private static final boolean DEBUG_SHARED_LIBRARIES = false;
     public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
     public static final boolean TRACE_SNAPSHOTS = false;
     private static final boolean DEBUG_PER_UID_READ_TIMEOUTS = false;
@@ -584,23 +557,6 @@
     private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE =
             150857253;
 
-    /**
-     * Apps targeting Android S and above need to declare dependencies to the public native
-     * shared libraries that are defined by the device maker using {@code uses-native-library} tag
-     * in its {@code AndroidManifest.xml}.
-     *
-     * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
-     * the package manager rejects to install the app. The dependency can be specified as optional
-     * using {@code android:required} attribute in the tag, in which case failing to satisfy the
-     * dependency doesn't stop the installation.
-     * <p>Once installed, an app is provided with only the native shared libraries that are
-     * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
-     * in the app manifest will fail even if it actually exists on the device.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
-    private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
-
     public static final String PLATFORM_PACKAGE_NAME = "android";
 
     static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
@@ -648,7 +604,7 @@
 
     private final boolean mEnableFreeCacheV2;
 
-    final int mSdkVersion;
+    private final int mSdkVersion;
     final Context mContext;
     final boolean mFactoryTest;
     final boolean mOnlyCore;
@@ -899,6 +855,7 @@
     final ArtManagerService mArtManagerService;
 
     final PackageDexOptimizer mPackageDexOptimizer;
+    final BackgroundDexOptService mBackgroundDexOptService;
     // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
     // is used by other apps).
     private final DexManager mDexManager;
@@ -931,7 +888,7 @@
     final ActivityInfo mResolveActivity = new ActivityInfo();
     final ResolveInfo mResolveInfo = new ResolveInfo();
     @Watched(manual = true)
-    private ComponentName mResolveComponentName;
+    ComponentName mResolveComponentName;
     AndroidPackage mPlatformPackage;
     ComponentName mCustomResolverComponentName;
 
@@ -1022,10 +979,6 @@
     final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<>();
     int mNextInstallToken = 1;  // nonzero; will be wrapped back to 1 when ++ overflows
 
-    // XML tags for backup/restore of various bits of state
-    private static final String TAG_PREFERRED_BACKUP = "pa";
-    private static final String TAG_DEFAULT_APPS = "da";
-
     final @Nullable String mRequiredVerifierPackage;
     final @NonNull String mRequiredInstallerPackage;
     final @NonNull String mRequiredUninstallerPackage;
@@ -1054,6 +1007,8 @@
     private final DeletePackageHelper mDeletePackageHelper;
     private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
     private final AppDataHelper mAppDataHelper;
+    private final PreferredActivityHelper mPreferredActivityHelper;
+    private final ResolveIntentHelper mResolveIntentHelper;
 
     /**
      * Invalidate the package info cache, which includes updating the cached computer.
@@ -1281,7 +1236,7 @@
      * Report a locally-detected change to observers.  The <what> parameter is left null,
      * but it signifies that the change was detected by PackageManagerService itself.
      */
-    private static void onChanged() {
+    static void onChanged() {
         onChange(null);
     }
 
@@ -1448,7 +1403,7 @@
         scheduleWritePackageRestrictionsLocked(userId);
     }
 
-    private void scheduleWritePackageRestrictionsLocked(int userId) {
+    void scheduleWritePackageRestrictionsLocked(int userId) {
         invalidatePackageInfoCache();
         final int[] userIds = (userId == UserHandle.USER_ALL)
                 ? mUserManager.getUserIds() : new int[]{userId};
@@ -1531,7 +1486,8 @@
                 },
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
-                context::getSystemService);
+                context::getSystemService,
+                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager()));
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1580,7 +1536,7 @@
         injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_R_CHANGES,
                 selinuxChangeListener);
 
-        m.installWhitelistedSystemPackages();
+        m.installAllowlistedSystemPackages();
         ServiceManager.addService("package", m);
         final PackageManagerNative pmn = m.new PackageManagerNative();
         ServiceManager.addService("package_native", pmn);
@@ -1588,7 +1544,7 @@
     }
 
     /** Install/uninstall system packages for all users based on their user-type, as applicable. */
-    private void installWhitelistedSystemPackages() {
+    private void installAllowlistedSystemPackages() {
         synchronized (mLock) {
             final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
                     isFirstBoot(), isDeviceUpgrading(), mExistingPackages);
@@ -1676,6 +1632,7 @@
         mApexManager = testParams.apexManager;
         mArtManagerService = testParams.artManagerService;
         mAvailableFeatures = testParams.availableFeatures;
+        mBackgroundDexOptService = testParams.backgroundDexOptService;
         mDefParseFlags = testParams.defParseFlags;
         mDefaultAppProvider = testParams.defaultAppProvider;
         mLegacyPermissionManager = testParams.legacyPermissionManagerInternal;
@@ -1748,6 +1705,8 @@
                 mAppDataHelper);
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
                 mInitAndSystemPackageHelper, mAppDataHelper);
+        mPreferredActivityHelper = new PreferredActivityHelper(this);
+        mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
 
         invalidatePackageInfoCache();
     }
@@ -1852,6 +1811,7 @@
 
         mPackageDexOptimizer = injector.getPackageDexOptimizer();
         mDexManager = injector.getDexManager();
+        mBackgroundDexOptService = injector.getBackgroundDexOptService();
         mArtManagerService = injector.getArtManagerService();
         mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
         mViewCompiler = injector.getViewCompiler();
@@ -1899,6 +1859,8 @@
                 mAppDataHelper);
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
                 mInitAndSystemPackageHelper, mAppDataHelper);
+        mPreferredActivityHelper = new PreferredActivityHelper(this);
+        mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
 
         synchronized (mLock) {
             // Create the computer as soon as the state objects have been installed.  The
@@ -2160,7 +2122,7 @@
             // Legacy existing (installed before Q) non-system apps to hide
             // their icons in launcher.
             if (!mOnlyCore && mIsPreQUpgrade) {
-                Slog.i(TAG, "Whitelisting all existing apps to hide their icons");
+                Slog.i(TAG, "Allowlisting all existing apps to hide their icons");
                 int size = packageSettings.size();
                 for (int i = 0; i < size; i++) {
                     final PackageSetting ps = packageSettings.valueAt(i);
@@ -2409,9 +2371,10 @@
     private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
         final Intent intent = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
 
-        final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
-                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, Binder.getCallingUid());
+        final List<ResolveInfo> matches =
+                mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+                        MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                        UserHandle.USER_SYSTEM, Binder.getCallingUid());
         if (matches.size() == 1) {
             return matches.get(0).getComponentInfo().packageName;
         } else if (matches.size() == 0) {
@@ -2505,9 +2468,10 @@
     private @NonNull ComponentName getIntentFilterVerifierComponentNameLPr() {
         final Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
 
-        final List<ResolveInfo> matches = queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
-                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, Binder.getCallingUid());
+        final List<ResolveInfo> matches =
+                mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+                        MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                        UserHandle.USER_SYSTEM, Binder.getCallingUid());
         ResolveInfo best = null;
         final int N = matches.size();
         for (int i = 0; i < N; i++) {
@@ -2533,9 +2497,10 @@
     @Nullable
     private ComponentName getDomainVerificationAgentComponentNameLPr() {
         Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
-        List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
-                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
-                UserHandle.USER_SYSTEM, Binder.getCallingUid());
+        List<ResolveInfo> matches =
+                mResolveIntentHelper.queryIntentReceiversInternal(intent, null,
+                        MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                        UserHandle.USER_SYSTEM, Binder.getCallingUid());
         ResolveInfo best = null;
         final int N = matches.size();
         for (int i = 0; i < N; i++) {
@@ -3257,7 +3222,7 @@
      * action and a {@code android.intent.category.BROWSABLE} category</li>
      * </ul>
      */
-    private int updateFlagsForResolve(int flags, int userId, int callingUid,
+    int updateFlagsForResolve(int flags, int userId, int callingUid,
             boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) {
         return mComputer.updateFlagsForResolve(flags, userId, callingUid,
                 wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc);
@@ -4033,27 +3998,6 @@
         }
     }
 
-    /**
-     * If the database version for this type of package (internal storage or
-     * external storage) is less than the version where package signatures
-     * were updated, return true.
-     */
-    boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isCompatSignatureUpdateNeeded(getSettingsVersionForPackage(pkg));
-    }
-
-    static boolean isCompatSignatureUpdateNeeded(VersionInfo ver) {
-        return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
-    }
-
-    boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isRecoverSignatureUpdateNeeded(getSettingsVersionForPackage(pkg));
-    }
-
-    static boolean isRecoverSignatureUpdateNeeded(VersionInfo ver) {
-        return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
-    }
-
     @Override
     public List<String> getAllPackages() {
         // Allow iorapd to call this method.
@@ -4318,124 +4262,25 @@
     @Override
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
             int flags, int userId) {
-        return resolveIntentInternal(intent, resolvedType, flags, 0 /*privateResolveFlags*/,
-                userId, false, Binder.getCallingUid());
-    }
-
-    /**
-     * Normally instant apps can only be resolved when they're visible to the caller.
-     * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
-     * since we need to allow the system to start any installed application.
-     */
-    private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
-            @PrivateResolveFlags int privateResolveFlags, int userId, boolean resolveForStart,
-            int filterCallingUid) {
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
-
-            if (!mUserManager.exists(userId)) return null;
-            final int callingUid = Binder.getCallingUid();
-            flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
-                    isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
-                            flags));
-            enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
-                    false /*checkShell*/, "resolve intent");
-
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
-            final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
-                    flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
-                    true /*allowDynamicSplits*/);
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-
-            final boolean queryMayBeFiltered =
-                    UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
-                            && !resolveForStart;
-
-            final ResolveInfo bestChoice =
-                    chooseBestActivity(
-                            intent, resolvedType, flags, privateResolveFlags, query, userId,
-                            queryMayBeFiltered);
-            final boolean nonBrowserOnly =
-                    (privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
-            if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
-                return null;
-            }
-            return bestChoice;
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
+        return mResolveIntentHelper.resolveIntentInternal(intent, resolvedType, flags,
+                0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
     }
 
     @Override
     public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
-        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
-            throw new SecurityException(
-                    "findPersistentPreferredActivity can only be run by the system");
-        }
-        if (!mUserManager.exists(userId)) {
-            return null;
-        }
-        final int callingUid = Binder.getCallingUid();
-        intent = PackageManagerServiceUtils.updateIntentForResolve(intent);
-        final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
-        final int flags = updateFlagsForResolve(
-                0, userId, callingUid, false /*includeInstantApps*/,
-                isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, 0));
-        final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
-                userId);
-        synchronized (mLock) {
-            return findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
-                    userId);
-        }
+        return mPreferredActivityHelper.findPersistentPreferredActivity(intent, userId);
     }
 
     @Override
     public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
             IntentFilter filter, int match, ComponentName activity) {
-        setLastChosenActivity(intent, resolvedType, flags,
+        mPreferredActivityHelper.setLastChosenActivity(intent, resolvedType, flags,
                               new WatchedIntentFilter(filter), match, activity);
     }
 
-    /**
-     * Variant that takes a {@link WatchedIntentFilter}
-     */
-    public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
-            WatchedIntentFilter filter, int match, ComponentName activity) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return;
-        }
-        final int userId = UserHandle.getCallingUserId();
-        if (DEBUG_PREFERRED) {
-            Log.v(TAG, "setLastChosenActivity intent=" + intent
-                + " resolvedType=" + resolvedType
-                + " flags=" + flags
-                + " filter=" + filter
-                + " match=" + match
-                + " activity=" + activity);
-            filter.dump(new PrintStreamPrinter(System.out), "    ");
-        }
-        intent.setComponent(null);
-        final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
-                userId);
-        // Find any earlier preferred or last chosen entries and nuke them
-        findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, false, true, false, userId);
-        // Add the new activity as the last chosen for this filter
-        addPreferredActivity(filter, match, null, activity, false, userId,
-                "Setting last chosen", false);
-    }
-
     @Override
     public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        final int userId = UserHandle.getCallingUserId();
-        if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
-        final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
-                userId);
-        return findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, false, false, false, userId);
+        return mPreferredActivityHelper.getLastChosenActivity(intent, resolvedType, flags);
     }
 
     private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -4450,115 +4295,6 @@
         mHandler.sendMessage(msg);
     }
 
-    private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
-            int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
-            boolean queryMayBeFiltered) {
-        if (query != null) {
-            final int N = query.size();
-            if (N == 1) {
-                return query.get(0);
-            } else if (N > 1) {
-                final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
-                // If there is more than one activity with the same priority,
-                // then let the user decide between them.
-                ResolveInfo r0 = query.get(0);
-                ResolveInfo r1 = query.get(1);
-                if (DEBUG_INTENT_MATCHING || debug) {
-                    Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
-                            + r1.activityInfo.name + "=" + r1.priority);
-                }
-                // If the first activity has a higher priority, or a different
-                // default, then it is always desirable to pick it.
-                if (r0.priority != r1.priority
-                        || r0.preferredOrder != r1.preferredOrder
-                        || r0.isDefault != r1.isDefault) {
-                    return query.get(0);
-                }
-                // If we have saved a preference for a preferred activity for
-                // this Intent, use that.
-                ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
-                        flags, query, true, false, debug, userId, queryMayBeFiltered);
-                if (ri != null) {
-                    return ri;
-                }
-                int browserCount = 0;
-                for (int i = 0; i < N; i++) {
-                    ri = query.get(i);
-                    if (ri.handleAllWebDataURI) {
-                        browserCount++;
-                    }
-                    // If we have an ephemeral app, use it
-                    if (ri.activityInfo.applicationInfo.isInstantApp()) {
-                        final String packageName = ri.activityInfo.packageName;
-                        final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                        if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
-                                mDomainVerificationManager, ps, intent, flags, userId)) {
-                            return ri;
-                        }
-                    }
-                }
-                if ((privateResolveFlags
-                        & PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
-                    return null;
-                }
-                ri = new ResolveInfo(mResolveInfo);
-                // if all resolve options are browsers, mark the resolver's info as if it were
-                // also a browser.
-                ri.handleAllWebDataURI = browserCount == N;
-                ri.activityInfo = new ActivityInfo(ri.activityInfo);
-                ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
-                // If all of the options come from the same package, show the application's
-                // label and icon instead of the generic resolver's.
-                // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
-                // and then throw away the ResolveInfo itself, meaning that the caller loses
-                // the resolvePackageName. Therefore the activityInfo.labelRes above provides
-                // a fallback for this case; we only set the target package's resources on
-                // the ResolveInfo, not the ActivityInfo.
-                final String intentPackage = intent.getPackage();
-                if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
-                    final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
-                    ri.resolvePackageName = intentPackage;
-                    if (userNeedsBadging(userId)) {
-                        ri.noResourceId = true;
-                    } else {
-                        ri.icon = appi.icon;
-                    }
-                    ri.iconResourceId = appi.icon;
-                    ri.labelRes = appi.labelRes;
-                }
-                ri.activityInfo.applicationInfo = new ApplicationInfo(
-                        ri.activityInfo.applicationInfo);
-                if (userId != 0) {
-                    ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
-                            UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
-                }
-                // Make sure that the resolver is displayable in car mode
-                if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
-                ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
-                return ri;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Return true if the given list is not empty and all of its contents have
-     * an activityInfo with the given package name.
-     */
-    private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
-        if (ArrayUtils.isEmpty(list)) {
-            return false;
-        }
-        for (int i = 0, N = list.size(); i < N; i++) {
-            final ResolveInfo ri = list.get(i);
-            final ActivityInfo ai = ri != null ? ri.activityInfo : null;
-            if (ai == null || !packageName.equals(ai.packageName)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     /**
      * From Android R, camera intents have to match system apps. The only exception to this is if
      * the DPC has set the camera persistent preferred activity. This case was introduced
@@ -4569,14 +4305,14 @@
      * activity was not set by the DPC.
      */
     @GuardedBy("mLock")
-    private boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
+    boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
             String resolvedType, int flags) {
         return mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
                 resolvedType, flags);
     }
 
     @GuardedBy("mLock")
-    private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+    ResolveInfo findPersistentPreferredActivityLP(Intent intent,
             String resolvedType,
             int flags, List<ResolveInfo> query, boolean debug, int userId) {
         return mComputer.findPersistentPreferredActivityLP(intent,
@@ -4591,7 +4327,7 @@
         ResolveInfo mPreferredResolveInfo;
     }
 
-    private FindPreferredActivityBodyResult findPreferredActivityInternal(
+    FindPreferredActivityBodyResult findPreferredActivityInternal(
             Intent intent, String resolvedType, int flags,
             List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
@@ -4601,42 +4337,6 @@
             removeMatches, debug, userId, queryMayBeFiltered);
     }
 
-    private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType,
-            int flags, List<ResolveInfo> query, boolean always, boolean removeMatches,
-            boolean debug, int userId) {
-        return findPreferredActivityNotLocked(
-                intent, resolvedType, flags, query, always, removeMatches, debug, userId,
-                UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
-    }
-
-    // TODO: handle preferred activities missing while user has amnesia
-    /** <b>must not hold {@link #mLock}</b> */
-    private ResolveInfo findPreferredActivityNotLocked(
-            Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always,
-            boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
-        if (Thread.holdsLock(mLock)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mLock", new Throwable());
-        }
-        if (!mUserManager.exists(userId)) return null;
-
-        FindPreferredActivityBodyResult body = findPreferredActivityInternal(
-                intent, resolvedType, flags, query, always,
-                removeMatches, debug, userId, queryMayBeFiltered);
-        if (body.mChanged) {
-            if (DEBUG_PREFERRED) {
-                Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
-            }
-            synchronized (mLock) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        }
-        if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
-            Slog.v(TAG, "No preferred activity to return");
-        }
-        return body.mPreferredResolveInfo;
-    }
-
     /*
      * Returns if intent can be forwarded from the sourceUserId to the targetUserId
      */
@@ -4701,17 +4401,17 @@
      * Returns the package name of the calling Uid if it's an instant app. If it isn't
      * instant, returns {@code null}.
      */
-    private String getInstantAppPackageName(int callingUid) {
+    String getInstantAppPackageName(int callingUid) {
         return mComputer.getInstantAppPackageName(callingUid);
     }
 
-    private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+    @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, int userId) {
         return mComputer.queryIntentActivitiesInternal(intent,
                 resolvedType, flags, userId);
     }
 
-    private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
+    @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) {
         return mComputer.queryIntentActivitiesInternal(intent,
@@ -4736,7 +4436,7 @@
      * @param intent
      * @return A filtered list of resolved activities.
      */
-    private List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
+    List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
             String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
             boolean resolveForStart, int userId, Intent intent) {
         return mComputer.applyPostResolutionFilter(resolveInfos,
@@ -4748,311 +4448,22 @@
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
             Intent[] specifics, String[] specificTypes, Intent intent,
             String resolvedType, int flags, int userId) {
-        return new ParceledListSlice<>(queryIntentActivityOptionsInternal(caller, specifics,
-                specificTypes, intent, resolvedType, flags, userId));
-    }
-
-    private @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
-            Intent[] specifics, String[] specificTypes, Intent intent,
-            String resolvedType, int flags, int userId) {
-        if (!mUserManager.exists(userId)) return Collections.emptyList();
-        final int callingUid = Binder.getCallingUid();
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
-                isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
-                        flags));
-        enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
-                false /*checkShell*/, "query intent activity options");
-        final String resultsAction = intent.getAction();
-
-        final List<ResolveInfo> results = queryIntentActivitiesInternal(intent, resolvedType, flags
-                | PackageManager.GET_RESOLVED_FILTER, userId);
-
-        if (DEBUG_INTENT_MATCHING) {
-            Log.v(TAG, "Query " + intent + ": " + results);
-        }
-
-        int specificsPos = 0;
-        int N;
-
-        // todo: note that the algorithm used here is O(N^2).  This
-        // isn't a problem in our current environment, but if we start running
-        // into situations where we have more than 5 or 10 matches then this
-        // should probably be changed to something smarter...
-
-        // First we go through and resolve each of the specific items
-        // that were supplied, taking care of removing any corresponding
-        // duplicate items in the generic resolve list.
-        if (specifics != null) {
-            for (int i=0; i<specifics.length; i++) {
-                final Intent sintent = specifics[i];
-                if (sintent == null) {
-                    continue;
-                }
-
-                if (DEBUG_INTENT_MATCHING) {
-                    Log.v(TAG, "Specific #" + i + ": " + sintent);
-                }
-
-                String action = sintent.getAction();
-                if (resultsAction != null && resultsAction.equals(action)) {
-                    // If this action was explicitly requested, then don't
-                    // remove things that have it.
-                    action = null;
-                }
-
-                ResolveInfo ri = null;
-                ActivityInfo ai = null;
-
-                ComponentName comp = sintent.getComponent();
-                if (comp == null) {
-                    ri = resolveIntent(
-                        sintent,
-                        specificTypes != null ? specificTypes[i] : null,
-                            flags, userId);
-                    if (ri == null) {
-                        continue;
-                    }
-                    if (ri == mResolveInfo) {
-                        // ACK!  Must do something better with this.
-                    }
-                    ai = ri.activityInfo;
-                    comp = new ComponentName(ai.applicationInfo.packageName,
-                            ai.name);
-                } else {
-                    ai = getActivityInfo(comp, flags, userId);
-                    if (ai == null) {
-                        continue;
-                    }
-                }
-
-                // Look for any generic query activities that are duplicates
-                // of this specific one, and remove them from the results.
-                if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
-                N = results.size();
-                int j;
-                for (j=specificsPos; j<N; j++) {
-                    ResolveInfo sri = results.get(j);
-                    if ((sri.activityInfo.name.equals(comp.getClassName())
-                            && sri.activityInfo.applicationInfo.packageName.equals(
-                                    comp.getPackageName()))
-                        || (action != null && sri.filter.matchAction(action))) {
-                        results.remove(j);
-                        if (DEBUG_INTENT_MATCHING) Log.v(
-                            TAG, "Removing duplicate item from " + j
-                            + " due to specific " + specificsPos);
-                        if (ri == null) {
-                            ri = sri;
-                        }
-                        j--;
-                        N--;
-                    }
-                }
-
-                // Add this specific item to its proper place.
-                if (ri == null) {
-                    ri = new ResolveInfo();
-                    ri.activityInfo = ai;
-                }
-                results.add(specificsPos, ri);
-                ri.specificIndex = i;
-                specificsPos++;
-            }
-        }
-
-        // Now we go through the remaining generic results and remove any
-        // duplicate actions that are found here.
-        N = results.size();
-        for (int i=specificsPos; i<N-1; i++) {
-            final ResolveInfo rii = results.get(i);
-            if (rii.filter == null) {
-                continue;
-            }
-
-            // Iterate over all of the actions of this result's intent
-            // filter...  typically this should be just one.
-            final Iterator<String> it = rii.filter.actionsIterator();
-            if (it == null) {
-                continue;
-            }
-            while (it.hasNext()) {
-                final String action = it.next();
-                if (resultsAction != null && resultsAction.equals(action)) {
-                    // If this action was explicitly requested, then don't
-                    // remove things that have it.
-                    continue;
-                }
-                for (int j=i+1; j<N; j++) {
-                    final ResolveInfo rij = results.get(j);
-                    if (rij.filter != null && rij.filter.hasAction(action)) {
-                        results.remove(j);
-                        if (DEBUG_INTENT_MATCHING) Log.v(
-                            TAG, "Removing duplicate item from " + j
-                            + " due to action " + action + " at " + i);
-                        j--;
-                        N--;
-                    }
-                }
-            }
-
-            // If the caller didn't request filter information, drop it now
-            // so we don't have to marshall/unmarshall it.
-            if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
-                rii.filter = null;
-            }
-        }
-
-        // Filter out the caller activity if so requested.
-        if (caller != null) {
-            N = results.size();
-            for (int i=0; i<N; i++) {
-                ActivityInfo ainfo = results.get(i).activityInfo;
-                if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
-                        && caller.getClassName().equals(ainfo.name)) {
-                    results.remove(i);
-                    break;
-                }
-            }
-        }
-
-        // If the caller didn't request filter information,
-        // drop them now so we don't have to
-        // marshall/unmarshall it.
-        if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
-            N = results.size();
-            for (int i=0; i<N; i++) {
-                results.get(i).filter = null;
-            }
-        }
-
-        if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
-        return results;
+        return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal(
+                caller, specifics, specificTypes, intent, resolvedType, flags, userId));
     }
 
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
             String resolvedType, int flags, int userId) {
-        return new ParceledListSlice<>(queryIntentReceiversInternal(intent, resolvedType,
-                flags, userId, Binder.getCallingUid()));
-    }
-
-    // In this method, we have to know the actual calling UID, but in some cases Binder's
-    // call identity is removed, so the UID has to be passed in explicitly.
-    private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
-            String resolvedType, int flags, int userId, int filterCallingUid) {
-        if (!mUserManager.exists(userId)) return Collections.emptyList();
-        enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
-                false /*checkShell*/, "query intent receivers");
-        final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
-        flags = updateFlagsForResolve(flags, userId, filterCallingUid, false /*includeInstantApps*/,
-                isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
-                        flags));
-        Intent originalIntent = null;
-        ComponentName comp = intent.getComponent();
-        if (comp == null) {
-            if (intent.getSelector() != null) {
-                originalIntent = intent;
-                intent = intent.getSelector();
-                comp = intent.getComponent();
-            }
-        }
-        List<ResolveInfo> list = Collections.emptyList();
-        if (comp != null) {
-            final ActivityInfo ai = getReceiverInfo(comp, flags, userId);
-            if (ai != null) {
-                // When specifying an explicit component, we prevent the activity from being
-                // used when either 1) the calling package is normal and the activity is within
-                // an instant application or 2) the calling package is ephemeral and the
-                // activity is not visible to instant applications.
-                final boolean matchInstantApp =
-                        (flags & PackageManager.MATCH_INSTANT) != 0;
-                final boolean matchVisibleToInstantAppOnly =
-                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
-                final boolean matchExplicitlyVisibleOnly =
-                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
-                final boolean isCallerInstantApp =
-                        instantAppPkgName != null;
-                final boolean isTargetSameInstantApp =
-                        comp.getPackageName().equals(instantAppPkgName);
-                final boolean isTargetInstantApp =
-                        (ai.applicationInfo.privateFlags
-                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-                final boolean isTargetVisibleToInstantApp =
-                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
-                final boolean isTargetExplicitlyVisibleToInstantApp =
-                        isTargetVisibleToInstantApp
-                        && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
-                final boolean isTargetHiddenFromInstantApp =
-                        !isTargetVisibleToInstantApp
-                        || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
-                final boolean blockResolution =
-                        !isTargetSameInstantApp
-                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
-                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
-                                        && isTargetHiddenFromInstantApp));
-                if (!blockResolution) {
-                    ResolveInfo ri = new ResolveInfo();
-                    ri.activityInfo = ai;
-                    list = new ArrayList<>(1);
-                    list.add(ri);
-                    PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                            mInjector.getCompatibility(), mComponentResolver,
-                            list, true, intent, resolvedType, filterCallingUid);
-                }
-            }
-        } else {
-            // reader
-            synchronized (mLock) {
-                String pkgName = intent.getPackage();
-                if (pkgName == null) {
-                    final List<ResolveInfo> result =
-                            mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
-                    if (result != null) {
-                        list = result;
-                    }
-                }
-                final AndroidPackage pkg = mPackages.get(pkgName);
-                if (pkg != null) {
-                    final List<ResolveInfo> result = mComponentResolver.queryReceivers(
-                            intent, resolvedType, flags, pkg.getReceivers(), userId);
-                    if (result != null) {
-                        list = result;
-                    }
-                }
-            }
-        }
-
-        if (originalIntent != null) {
-            // We also have to ensure all components match the original intent
-            PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
-                    mInjector.getCompatibility(), mComponentResolver,
-                    list, true, originalIntent, resolvedType, filterCallingUid);
-        }
-
-        return applyPostResolutionFilter(
-                list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
+        return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(intent,
+                resolvedType, flags, userId, Binder.getCallingUid()));
     }
 
     @Override
     public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
         final int callingUid = Binder.getCallingUid();
-        return resolveServiceInternal(intent, resolvedType, flags, userId, callingUid);
-    }
-
-    private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
-            int userId, int callingUid) {
-        if (!mUserManager.exists(userId)) return null;
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
-                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
-        List<ResolveInfo> query = queryIntentServicesInternal(
-                intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
-        if (query != null) {
-            if (query.size() >= 1) {
-                // If there is more than one service with the same priority,
-                // just arbitrarily pick the first one.
-                return query.get(0);
-            }
-        }
-        return null;
+        return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
+                callingUid);
     }
 
     @Override
@@ -5063,7 +4474,7 @@
                 intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/));
     }
 
-    private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
+    @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         return mComputer.queryIntentServicesInternal(intent,
@@ -5074,148 +4485,8 @@
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
             String resolvedType, int flags, int userId) {
-        return new ParceledListSlice<>(
-                queryIntentContentProvidersInternal(intent, resolvedType, flags, userId));
-    }
-
-    private @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
-            Intent intent, String resolvedType, int flags, int userId) {
-        if (!mUserManager.exists(userId)) return Collections.emptyList();
-        final int callingUid = Binder.getCallingUid();
-        final String instantAppPkgName = getInstantAppPackageName(callingUid);
-        flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
-                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
-        ComponentName comp = intent.getComponent();
-        if (comp == null) {
-            if (intent.getSelector() != null) {
-                intent = intent.getSelector();
-                comp = intent.getComponent();
-            }
-        }
-        if (comp != null) {
-            final List<ResolveInfo> list = new ArrayList<>(1);
-            final ProviderInfo pi = getProviderInfo(comp, flags, userId);
-            if (pi != null) {
-                // When specifying an explicit component, we prevent the provider from being
-                // used when either 1) the provider is in an instant application and the
-                // caller is not the same instant application or 2) the calling package is an
-                // instant application and the provider is not visible to instant applications.
-                final boolean matchInstantApp =
-                        (flags & PackageManager.MATCH_INSTANT) != 0;
-                final boolean matchVisibleToInstantAppOnly =
-                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
-                final boolean isCallerInstantApp =
-                        instantAppPkgName != null;
-                final boolean isTargetSameInstantApp =
-                        comp.getPackageName().equals(instantAppPkgName);
-                final boolean isTargetInstantApp =
-                        (pi.applicationInfo.privateFlags
-                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-                final boolean isTargetHiddenFromInstantApp =
-                        (pi.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
-                final boolean blockResolution =
-                        !isTargetSameInstantApp
-                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
-                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
-                                        && isTargetHiddenFromInstantApp));
-                final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
-                        && shouldFilterApplicationLocked(
-                        getPackageSettingInternal(pi.applicationInfo.packageName,
-                                Process.SYSTEM_UID), callingUid, userId);
-                if (!blockResolution && !blockNormalResolution) {
-                    final ResolveInfo ri = new ResolveInfo();
-                    ri.providerInfo = pi;
-                    list.add(ri);
-                }
-            }
-            return list;
-        }
-
-        // reader
-        synchronized (mLock) {
-            String pkgName = intent.getPackage();
-            if (pkgName == null) {
-                final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
-                        resolvedType, flags, userId);
-                if (resolveInfos == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostContentProviderResolutionFilter(
-                        resolveInfos, instantAppPkgName, userId, callingUid);
-            }
-            final AndroidPackage pkg = mPackages.get(pkgName);
-            if (pkg != null) {
-                final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
-                        resolvedType, flags,
-                        pkg.getProviders(), userId);
-                if (resolveInfos == null) {
-                    return Collections.emptyList();
-                }
-                return applyPostContentProviderResolutionFilter(
-                        resolveInfos, instantAppPkgName, userId, callingUid);
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private List<ResolveInfo> applyPostContentProviderResolutionFilter(
-            List<ResolveInfo> resolveInfos, String instantAppPkgName,
-            @UserIdInt int userId, int callingUid) {
-        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
-            final ResolveInfo info = resolveInfos.get(i);
-
-            if (instantAppPkgName == null) {
-                SettingBase callingSetting =
-                        mSettings.getSettingLPr(UserHandle.getAppId(callingUid));
-                PackageSetting resolvedSetting =
-                        getPackageSettingInternal(info.providerInfo.packageName, 0);
-                if (!mAppsFilter.shouldFilterApplication(
-                        callingUid, callingSetting, resolvedSetting, userId)) {
-                    continue;
-                }
-            }
-
-            final boolean isEphemeralApp = info.providerInfo.applicationInfo.isInstantApp();
-            // allow providers that are defined in the provided package
-            if (isEphemeralApp && instantAppPkgName.equals(info.providerInfo.packageName)) {
-                if (info.providerInfo.splitName != null
-                        && !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
-                                info.providerInfo.splitName)) {
-                    if (mInstantAppInstallerActivity == null) {
-                        if (DEBUG_INSTANT) {
-                            Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
-                        }
-                        resolveInfos.remove(i);
-                        continue;
-                    }
-                    // requested provider is defined in a split that hasn't been installed yet.
-                    // add the installer to the resolve list
-                    if (DEBUG_INSTANT) {
-                        Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
-                    }
-                    final ResolveInfo installerInfo = new ResolveInfo(
-                            mInstantAppInstallerInfo);
-                    installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
-                            null /*failureActivity*/,
-                            info.providerInfo.packageName,
-                            info.providerInfo.applicationInfo.longVersionCode,
-                            info.providerInfo.splitName);
-                    // add a non-generic filter
-                    installerInfo.filter = new IntentFilter();
-                    // load resources from the correct package
-                    installerInfo.resolvePackageName = info.getComponentInfo().packageName;
-                    resolveInfos.set(i, installerInfo);
-                }
-                continue;
-            }
-            // allow providers that have been explicitly exposed to instant applications
-            if (!isEphemeralApp
-                    && ((info.providerInfo.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
-                continue;
-            }
-            resolveInfos.remove(i);
-        }
-        return resolveInfos;
+        return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal(
+                intent, resolvedType, flags, userId));
     }
 
     @Override
@@ -5459,7 +4730,7 @@
         }
     }
 
-    private boolean isCallerSameApp(String packageName, int uid) {
+    boolean isCallerSameApp(String packageName, int uid) {
         return mComputer.isCallerSameApp(packageName, uid);
     }
 
@@ -6140,6 +5411,10 @@
         }
     }
 
+    /*package*/ void controlDexOptBlocking(boolean block) {
+        mPackageDexOptimizer.controlDexOptBlocking(block);
+    }
+
     /**
      * Perform dexopt on the given package and return one of following result:
      *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
@@ -6276,23 +5551,6 @@
         return mDexManager;
     }
 
-    /**
-     * Execute the background dexopt job immediately.
-     */
-    @Override
-    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return false;
-        }
-        enforceSystemOrRootOrShell("runBackgroundDexoptJob");
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     private static List<SharedLibraryInfo> findSharedLibraries(PackageSetting pkgSetting) {
         if (!pkgSetting.getPkgState().getUsesLibraryInfos().isEmpty()) {
             ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
@@ -6344,27 +5602,6 @@
         return mComputer.getSharedLibraryInfoLPr(name, version);
     }
 
-    @Nullable
-    public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
-            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
-        if (newLibraries != null) {
-            final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
-            SharedLibraryInfo info = null;
-            if (versionedLib != null) {
-                info = versionedLib.get(version);
-            }
-            if (info != null) {
-                return info;
-            }
-        }
-        final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
-        if (versionedLib == null) {
-            return null;
-        }
-        return versionedLib.get(version);
-    }
-
     SharedLibraryInfo getLatestSharedLibraVersionLPr(AndroidPackage pkg) {
         WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                 pkg.getStaticSharedLibName());
@@ -6544,58 +5781,15 @@
             @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting,
             Map<String, AndroidPackage> availablePackages)
             throws PackageManagerException {
-        final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
-                pkgSetting.getPkg(), availablePackages, mSharedLibraries, null /* newLibraries */,
-                mInjector.getCompatibility());
+        final ArrayList<SharedLibraryInfo> sharedLibraryInfos =
+                SharedLibraryHelper.collectSharedLibraryInfos(
+                        pkgSetting.getPkg(), availablePackages, mSharedLibraries,
+                        null /* newLibraries */, mInjector.getCompatibility());
         executeSharedLibrariesUpdateLPr(pkg, pkgSetting, changingLib, changingLibSetting,
                 sharedLibraryInfos, mUserManager.getUserIds());
     }
 
-    private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
-            Map<String, AndroidPackage> availablePackages,
-            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
-            PlatformCompat platformCompat) throws PackageManagerException {
-        if (pkg == null) {
-            return null;
-        }
-        // The collection used here must maintain the order of addition (so
-        // that libraries are searched in the correct order) and must have no
-        // duplicates.
-        ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
-        if (!pkg.getUsesLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
-                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), null,
-                    availablePackages, existingLibraries, newLibraries);
-        }
-        if (!pkg.getUsesStaticLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
-                    pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), usesLibraryInfos,
-                    availablePackages, existingLibraries, newLibraries);
-        }
-        if (!pkg.getUsesOptionalLibraries().isEmpty()) {
-            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(),
-                    null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
-        }
-        if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
-                pkg.getPackageName(), pkg.getTargetSdkVersion())) {
-            if (!pkg.getUsesNativeLibraries().isEmpty()) {
-                usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
-                        null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
-                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
-            }
-            if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
-                usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
-                        null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
-                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
-            }
-        }
-        return usesLibraryInfos;
-    }
-
-    private void executeSharedLibrariesUpdateLPr(AndroidPackage pkg,
+    void executeSharedLibrariesUpdateLPr(AndroidPackage pkg,
             @NonNull PackageSetting pkgSetting, @Nullable AndroidPackage changingLib,
             @Nullable PackageSetting changingLibSetting,
             ArrayList<SharedLibraryInfo> usesLibraryInfos, int[] allUsers) {
@@ -6643,102 +5837,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
-            @NonNull List<String> requestedLibraries,
-            @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
-            @NonNull String packageName, boolean required, int targetSdk,
-            @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
-            @NonNull final Map<String, AndroidPackage> availablePackages,
-            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
-            throws PackageManagerException {
-        final int libCount = requestedLibraries.size();
-        for (int i = 0; i < libCount; i++) {
-            final String libName = requestedLibraries.get(i);
-            final long libVersion = requiredVersions != null ? requiredVersions[i]
-                    : SharedLibraryInfo.VERSION_UNDEFINED;
-            final SharedLibraryInfo libraryInfo =
-                    getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries);
-            if (libraryInfo == null) {
-                if (required) {
-                    throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                            "Package " + packageName + " requires unavailable shared library "
-                                    + libName + "; failing!");
-                } else if (DEBUG_SHARED_LIBRARIES) {
-                    Slog.i(TAG, "Package " + packageName
-                            + " desires unavailable shared library "
-                            + libName + "; ignoring!");
-                }
-            } else {
-                if (requiredVersions != null && requiredCertDigests != null) {
-                    if (libraryInfo.getLongVersion() != requiredVersions[i]) {
-                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                            "Package " + packageName + " requires unavailable static shared"
-                                    + " library " + libName + " version "
-                                    + libraryInfo.getLongVersion() + "; failing!");
-                    }
-                    AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
-                    SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
-                    if (libPkg == null) {
-                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                "Package " + packageName + " requires unavailable static shared"
-                                        + " library; failing!");
-                    }
-                    final String[] expectedCertDigests = requiredCertDigests[i];
-                    if (expectedCertDigests.length > 1) {
-                        // For apps targeting O MR1 we require explicit enumeration of all certs.
-                        final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
-                                ? PackageUtils.computeSignaturesSha256Digests(
-                                libPkg.getSignatures())
-                                : PackageUtils.computeSignaturesSha256Digests(
-                                        new Signature[]{libPkg.getSignatures()[0]});
-
-                        // Take a shortcut if sizes don't match. Note that if an app doesn't
-                        // target O we don't parse the "additional-certificate" tags similarly
-                        // how we only consider all certs only for apps targeting O (see above).
-                        // Therefore, the size check is safe to make.
-                        if (expectedCertDigests.length != libCertDigests.length) {
-                            throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                    "Package " + packageName + " requires differently signed" +
-                                            " static shared library; failing!");
-                        }
-
-                        // Use a predictable order as signature order may vary
-                        Arrays.sort(libCertDigests);
-                        Arrays.sort(expectedCertDigests);
-
-                        final int certCount = libCertDigests.length;
-                        for (int j = 0; j < certCount; j++) {
-                            if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
-                                throw new PackageManagerException(
-                                        INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                        "Package " + packageName + " requires differently signed" +
-                                                " static shared library; failing!");
-                            }
-                        }
-                    } else {
-                        // lib signing cert could have rotated beyond the one expected, check to see
-                        // if the new one has been blessed by the old
-                        byte[] digestBytes = HexEncoding.decode(
-                                expectedCertDigests[0], false /* allowSingleChar */);
-                        if (!libPkg.hasSha256Certificate(digestBytes)) {
-                            throw new PackageManagerException(
-                                    INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                                    "Package " + packageName + " requires differently signed" +
-                                            " static shared library; failing!");
-                        }
-                    }
-                }
-                if (outUsedLibraries == null) {
-                    outUsedLibraries = new ArrayList<>();
-                }
-                outUsedLibraries.add(libraryInfo);
-            }
-        }
-        return outUsedLibraries;
-    }
-
     private static boolean hasString(List<String> list, List<String> which) {
         if (list == null || which == null) {
             return false;
@@ -6754,7 +5852,7 @@
     }
 
     @GuardedBy("mLock")
-    private ArrayList<AndroidPackage> updateAllSharedLibrariesLocked(
+    ArrayList<AndroidPackage> updateAllSharedLibrariesLocked(
             @Nullable AndroidPackage updatedPkg, @Nullable PackageSetting updatedPkgSetting,
             Map<String, AndroidPackage> availablePackages) {
         ArrayList<AndroidPackage> resultList = null;
@@ -6820,130 +5918,6 @@
         return resultList;
     }
 
-    /**
-     * Commits the package scan and modifies system state.
-     * <p><em>WARNING:</em> The method may throw an excpetion in the middle
-     * of committing the package, leaving the system in an inconsistent state.
-     * This needs to be fixed so, once we get to this point, no errors are
-     * possible and the system is not left in an inconsistent state.
-     */
-    @GuardedBy({"mLock", "mInstallLock"})
-    AndroidPackage commitReconciledScanResultLocked(
-            @NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
-        final ScanResult result = reconciledPkg.mScanResult;
-        final ScanRequest request = result.mRequest;
-        // TODO(b/135203078): Move this even further away
-        ParsedPackage parsedPackage = request.mParsedPackage;
-        if ("android".equals(parsedPackage.getPackageName())) {
-            // TODO(b/135203078): Move this to initial parse
-            parsedPackage.setVersionCode(mSdkVersion)
-                    .setVersionCodeMajor(0);
-        }
-
-        final AndroidPackage oldPkg = request.mOldPkg;
-        final @ParseFlags int parseFlags = request.mParseFlags;
-        final @ScanFlags int scanFlags = request.mScanFlags;
-        final PackageSetting oldPkgSetting = request.mOldPkgSetting;
-        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
-        final UserHandle user = request.mUser;
-        final String realPkgName = request.mRealPkgName;
-        final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
-        final PackageSetting pkgSetting;
-        if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null
-                && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) {
-            // shared user changed, remove from old shared user
-            request.mPkgSetting.getSharedUser().removePackage(request.mPkgSetting);
-        }
-        if (result.mExistingSettingCopied) {
-            pkgSetting = request.mPkgSetting;
-            pkgSetting.updateFrom(result.mPkgSetting);
-        } else {
-            pkgSetting = result.mPkgSetting;
-            if (originalPkgSetting != null) {
-                mSettings.addRenamedPackageLPw(
-                        AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
-                        originalPkgSetting.getPackageName());
-                mTransferredPackages.add(originalPkgSetting.getPackageName());
-            } else {
-                mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
-            }
-        }
-        if (pkgSetting.getSharedUser() != null) {
-            pkgSetting.getSharedUser().addPackage(pkgSetting);
-        }
-        if (reconciledPkg.mInstallArgs != null
-                        && reconciledPkg.mInstallArgs.mForceQueryableOverride) {
-            pkgSetting.setForceQueryableOverride(true);
-        }
-
-        // If this is part of a standard install, set the initiating package name, else rely on
-        // previous device state.
-        if (reconciledPkg.mInstallArgs != null) {
-            InstallSource installSource = reconciledPkg.mInstallArgs.mInstallSource;
-            if (installSource.initiatingPackageName != null) {
-                final PackageSetting ips = mSettings.getPackageLPr(
-                        installSource.initiatingPackageName);
-                if (ips != null) {
-                    installSource = installSource.setInitiatingPackageSignatures(
-                            ips.getSignatures());
-                }
-            }
-            pkgSetting.setInstallSource(installSource);
-        }
-
-        // TODO(toddke): Consider a method specifically for modifying the Package object
-        // post scan; or, moving this stuff out of the Package object since it has nothing
-        // to do with the package on disk.
-        // We need to have this here because addUserToSettingLPw() is sometimes responsible
-        // for creating the application ID. If we did this earlier, we would be saving the
-        // correct ID.
-        parsedPackage.setUid(pkgSetting.getAppId());
-        final AndroidPackage pkg = parsedPackage.hideAsFinal();
-
-        mSettings.writeUserRestrictionsLPw(pkgSetting, oldPkgSetting);
-
-        if (realPkgName != null) {
-            mTransferredPackages.add(pkg.getPackageName());
-        }
-
-        if (reconciledPkg.mCollectedSharedLibraryInfos != null) {
-            executeSharedLibrariesUpdateLPr(pkg, pkgSetting, null, null,
-                    reconciledPkg.mCollectedSharedLibraryInfos, allUsers);
-        }
-
-        final KeySetManagerService ksms = mSettings.getKeySetManagerService();
-        if (reconciledPkg.mRemoveAppKeySetData) {
-            ksms.removeAppKeySetDataLPw(pkg.getPackageName());
-        }
-        if (reconciledPkg.mSharedUserSignaturesChanged) {
-            pkgSetting.getSharedUser().signaturesChanged = Boolean.TRUE;
-            pkgSetting.getSharedUser().signatures.mSigningDetails = reconciledPkg.mSigningDetails;
-        }
-        pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
-
-        if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
-            for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
-                final String codePathString = changedAbiCodePath.get(i);
-                try {
-                    mInstaller.rmdex(codePathString,
-                            getDexCodeInstructionSet(getPreferredInstructionSet()));
-                } catch (InstallerException ignored) {
-                }
-            }
-        }
-
-        final int userId = user == null ? 0 : user.getIdentifier();
-        // Modify state for the given package setting
-        commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
-                (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
-        if (pkgSetting.getInstantApp(userId)) {
-            mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.getAppId());
-        }
-        pkgSetting.setStatesOnCommit();
-
-        return pkg;
-    }
-
     @GuardedBy("mLock")
     private void addBuiltInSharedLibraryLocked(SystemConfig.SharedLibraryEntry entry) {
         if (nonStaticSharedLibExistsLocked(entry.name)) {
@@ -6961,17 +5935,12 @@
 
     @GuardedBy("mLock")
     private boolean nonStaticSharedLibExistsLocked(String name) {
-        return sharedLibExists(name, SharedLibraryInfo.VERSION_UNDEFINED, mSharedLibraries);
-    }
-
-    private static boolean sharedLibExists(final String name, final long version,
-            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
-        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
-        return versionedLib != null && versionedLib.indexOfKey(version) >= 0;
+        return SharedLibraryHelper.sharedLibExists(name, SharedLibraryInfo.VERSION_UNDEFINED,
+                mSharedLibraries);
     }
 
     @GuardedBy("mLock")
-    private void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
+    void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
         final String name = libraryInfo.getName();
         WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
         if (versionedLib == null) {
@@ -6985,44 +5954,6 @@
         versionedLib.put(libraryInfo.getLongVersion(), libraryInfo);
     }
 
-    boolean removeSharedLibraryLPw(String name, long version) {
-        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
-        if (versionedLib == null) {
-            return false;
-        }
-        final int libIdx = versionedLib.indexOfKey(version);
-        if (libIdx < 0) {
-            return false;
-        }
-        SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
-
-        // Remove the shared library overlays from its dependent packages.
-        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
-            final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
-                    libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
-            if (dependents == null) {
-                continue;
-            }
-            for (VersionedPackage dependentPackage : dependents) {
-                final PackageSetting ps = mSettings.getPackageLPr(
-                        dependentPackage.getPackageName());
-                if (ps != null) {
-                    ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
-                }
-            }
-        }
-
-        versionedLib.remove(version);
-        if (versionedLib.size() <= 0) {
-            mSharedLibraries.remove(name);
-            if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) {
-                mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage()
-                        .getPackageName());
-            }
-        }
-        return true;
-    }
-
     @Override
     public Property getProperty(String propertyName, String packageName, String className) {
         Objects.requireNonNull(propertyName);
@@ -7054,201 +5985,6 @@
         return new ParceledListSlice<>(result);
     }
 
-    /**
-     * Adds a scanned package to the system. When this method is finished, the package will
-     * be available for query, resolution, etc...
-     */
-    private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
-            @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
-            final @ScanFlags int scanFlags, boolean chatty, ReconciledPackage reconciledPkg) {
-        final String pkgName = pkg.getPackageName();
-        if (mCustomResolverComponentName != null &&
-                mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
-            setUpCustomResolverActivity(pkg, pkgSetting);
-        }
-
-        if (pkg.getPackageName().equals("android")) {
-            synchronized (mLock) {
-                // Set up information for our fall-back user intent resolution activity.
-                mPlatformPackage = pkg;
-
-                // The instance stored in PackageManagerService is special cased to be non-user
-                // specific, so initialize all the needed fields here.
-                mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                        PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
-
-                if (!mResolverReplaced) {
-                    mResolveActivity.applicationInfo = mAndroidApplication;
-                    mResolveActivity.name = ResolverActivity.class.getName();
-                    mResolveActivity.packageName = mAndroidApplication.packageName;
-                    mResolveActivity.processName = "system:ui";
-                    mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
-                    mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
-                    mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
-                    mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
-                    mResolveActivity.exported = true;
-                    mResolveActivity.enabled = true;
-                    mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
-                    mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
-                            | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
-                            | ActivityInfo.CONFIG_SCREEN_LAYOUT
-                            | ActivityInfo.CONFIG_ORIENTATION
-                            | ActivityInfo.CONFIG_KEYBOARD
-                            | ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
-                    mResolveInfo.activityInfo = mResolveActivity;
-                    mResolveInfo.priority = 0;
-                    mResolveInfo.preferredOrder = 0;
-                    mResolveInfo.match = 0;
-                    mResolveComponentName = new ComponentName(
-                            mAndroidApplication.packageName, mResolveActivity.name);
-                }
-                onChanged();
-            }
-        }
-
-        ArrayList<AndroidPackage> clientLibPkgs = null;
-        // writer
-        synchronized (mLock) {
-            if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) {
-                for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) {
-                    commitSharedLibraryInfoLocked(info);
-                }
-                final Map<String, AndroidPackage> combinedSigningDetails =
-                        reconciledPkg.getCombinedAvailablePackages();
-                try {
-                    // Shared libraries for the package need to be updated.
-                    updateSharedLibrariesLocked(pkg, pkgSetting, null, null,
-                            combinedSigningDetails);
-                } catch (PackageManagerException e) {
-                    Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e);
-                }
-                // Update all applications that use this library. Skip when booting
-                // since this will be done after all packages are scaned.
-                if ((scanFlags & SCAN_BOOTING) == 0) {
-                    clientLibPkgs = updateAllSharedLibrariesLocked(pkg, pkgSetting,
-                            combinedSigningDetails);
-                }
-            }
-        }
-        if (reconciledPkg.mInstallResult != null) {
-            reconciledPkg.mInstallResult.mLibraryConsumers = clientLibPkgs;
-        }
-
-        if ((scanFlags & SCAN_BOOTING) != 0) {
-            // No apps can run during boot scan, so they don't need to be frozen
-        } else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {
-            // Caller asked to not kill app, so it's probably not frozen
-        } else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {
-            // Caller asked us to ignore frozen check for some reason; they
-            // probably didn't know the package name
-        } else {
-            // We're doing major surgery on this package, so it better be frozen
-            // right now to keep it from launching
-            checkPackageFrozen(pkgName);
-        }
-
-        // Also need to kill any apps that are dependent on the library.
-        if (clientLibPkgs != null) {
-            for (int i=0; i<clientLibPkgs.size(); i++) {
-                AndroidPackage clientPkg = clientLibPkgs.get(i);
-                killApplication(clientPkg.getPackageName(),
-                        clientPkg.getUid(), "update lib");
-            }
-        }
-
-        // writer
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
-
-        synchronized (mLock) {
-            // We don't expect installation to fail beyond this point
-            // Add the new setting to mSettings
-            mSettings.insertPackageSettingLPw(pkgSetting, pkg);
-            // Add the new setting to mPackages
-            mPackages.put(pkg.getPackageName(), pkg);
-            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
-                mApexManager.registerApkInApex(pkg);
-            }
-
-            // Add the package's KeySets to the global KeySetManagerService
-            KeySetManagerService ksms = mSettings.getKeySetManagerService();
-            ksms.addScannedPackageLPw(pkg);
-
-            mComponentResolver.addAllComponents(pkg, chatty);
-            final boolean isReplace =
-                    reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
-            mAppsFilter.addPackage(pkgSetting, isReplace);
-            mPackageProperty.addAllProperties(pkg);
-
-            if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
-                mDomainVerificationManager.addPackage(pkgSetting);
-            } else {
-                mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
-            }
-
-            int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
-            StringBuilder r = null;
-            int i;
-            for (i = 0; i < collectionSize; i++) {
-                ParsedInstrumentation a = pkg.getInstrumentations().get(i);
-                a.setPackageName(pkg.getPackageName());
-                mInstrumentation.put(a.getComponentName(), a);
-                if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(a.getName());
-                }
-            }
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Instrumentation: " + r);
-            }
-
-            final List<String> protectedBroadcasts = pkg.getProtectedBroadcasts();
-            if (!protectedBroadcasts.isEmpty()) {
-                synchronized (mProtectedBroadcasts) {
-                    mProtectedBroadcasts.addAll(protectedBroadcasts);
-                }
-            }
-
-            mPermissionManager.onPackageAdded(pkg, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
-        }
-
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    private void setUpCustomResolverActivity(AndroidPackage pkg, PackageSetting pkgSetting) {
-        synchronized (mLock) {
-            mResolverReplaced = true;
-
-            // The instance created in PackageManagerService is special cased to be non-user
-            // specific, so initialize all the needed fields here.
-            ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
-
-            // Set up information for custom user intent resolution activity.
-            mResolveActivity.applicationInfo = appInfo;
-            mResolveActivity.name = mCustomResolverComponentName.getClassName();
-            mResolveActivity.packageName = pkg.getPackageName();
-            mResolveActivity.processName = pkg.getProcessName();
-            mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
-            mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
-                    ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
-            mResolveActivity.theme = 0;
-            mResolveActivity.exported = true;
-            mResolveActivity.enabled = true;
-            mResolveInfo.activityInfo = mResolveActivity;
-            mResolveInfo.priority = 0;
-            mResolveInfo.preferredOrder = 0;
-            mResolveInfo.match = 0;
-            mResolveComponentName = mCustomResolverComponentName;
-            onChanged();
-            Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " +
-                    mResolveComponentName);
-        }
-    }
-
     private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) {
         if (installerActivity == null) {
             if (DEBUG_INSTANT) {
@@ -7278,7 +6014,7 @@
         onChanged();
     }
 
-    private void killApplication(String pkgName, @AppIdInt int appId, String reason) {
+    void killApplication(String pkgName, @AppIdInt int appId, String reason) {
         killApplication(pkgName, appId, UserHandle.USER_ALL, reason);
     }
 
@@ -7366,7 +6102,7 @@
         }
     }
 
-    private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
+    void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting,
             int userId, int dataLoaderType) {
         final boolean isSystem = PackageManagerServiceUtils.isSystemApp(pkgSetting)
                 || PackageManagerServiceUtils.isUpdatedSystemApp(pkgSetting);
@@ -7652,136 +6388,10 @@
     @Override
     public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
             int installReason, List<String> whiteListedPermissions) {
-        return installExistingPackageAsUser(packageName, userId, installFlags, installReason,
-                whiteListedPermissions, null);
-    }
-
-    int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
-            @PackageManager.InstallFlags int installFlags,
-            @PackageManager.InstallReason int installReason,
-            @Nullable List<String> allowlistedRestrictedPermissions,
-            @Nullable IntentSender intentSender) {
-        if (DEBUG_INSTALL) {
-            Log.v(TAG, "installExistingPackageAsUser package=" + packageName + " userId=" + userId
-                    + " installFlags=" + installFlags + " installReason=" + installReason
-                    + " allowlistedRestrictedPermissions=" + allowlistedRestrictedPermissions);
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
-                != PackageManager.PERMISSION_GRANTED
-                && mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Neither user " + callingUid + " nor current process has "
-                    + android.Manifest.permission.INSTALL_PACKAGES + ".");
-        }
-        PackageSetting pkgSetting;
-        enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                true /* checkShell */, "installExistingPackage for user " + userId);
-        if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
-            return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
-        }
-
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            boolean installed = false;
-            final boolean instantApp =
-                    (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
-            final boolean fullApp =
-                    (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
-
-            // writer
-            synchronized (mLock) {
-                pkgSetting = mSettings.getPackageLPr(packageName);
-                if (pkgSetting == null) {
-                    return PackageManager.INSTALL_FAILED_INVALID_URI;
-                }
-                if (!canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
-                    // only allow the existing package to be used if it's installed as a full
-                    // application for at least one user
-                    boolean installAllowed = false;
-                    for (int checkUserId : mUserManager.getUserIds()) {
-                        installAllowed = !pkgSetting.getInstantApp(checkUserId);
-                        if (installAllowed) {
-                            break;
-                        }
-                    }
-                    if (!installAllowed) {
-                        return PackageManager.INSTALL_FAILED_INVALID_URI;
-                    }
-                }
-                if (!pkgSetting.getInstalled(userId)) {
-                    pkgSetting.setInstalled(true, userId);
-                    pkgSetting.setHidden(false, userId);
-                    pkgSetting.setInstallReason(installReason, userId);
-                    pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId);
-                    mSettings.writePackageRestrictionsLPr(userId);
-                    mSettings.writeKernelMappingLPr(pkgSetting);
-                    installed = true;
-                } else if (fullApp && pkgSetting.getInstantApp(userId)) {
-                    // upgrade app from instant to full; we don't allow app downgrade
-                    installed = true;
-                }
-                setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
-            }
-
-            if (installed) {
-                if (pkgSetting.getPkg() != null) {
-                    final PermissionManagerServiceInternal.PackageInstalledParams.Builder
-                            permissionParamsBuilder =
-                            new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
-                    if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS)
-                            != 0) {
-                        permissionParamsBuilder.setAllowlistedRestrictedPermissions(
-                                pkgSetting.getPkg().getRequestedPermissions());
-                    }
-                    mPermissionManager.onPackageInstalled(pkgSetting.getPkg(),
-                            Process.INVALID_UID /* previousAppId */,
-                            permissionParamsBuilder.build(), userId);
-                }
-
-                if (pkgSetting.getPkg() != null) {
-                    synchronized (mInstallLock) {
-                        // We don't need to freeze for a brand new install
-                        mAppDataHelper.prepareAppDataAfterInstallLIF(pkgSetting.getPkg());
-                    }
-                }
-                sendPackageAddedForUser(packageName, pkgSetting, userId, DataLoaderType.NONE);
-                synchronized (mLock) {
-                    updateSequenceNumberLP(pkgSetting, new int[]{ userId });
-                }
-                // start async restore with no post-install since we finish install here
-                PackageInstalledInfo res = new PackageInstalledInfo(
-                        PackageManager.INSTALL_SUCCEEDED);
-                res.mPkg = pkgSetting.getPkg();
-                res.mNewUsers = new int[]{ userId };
-
-                PostInstallData postInstallData =
-                        new PostInstallData(null, res, () -> {
-                            restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
-                                    userId);
-                            if (intentSender != null) {
-                                onRestoreComplete(res.mReturnCode, mContext, intentSender);
-                            }
-                        });
-                restoreAndPostInstall(userId, res, postInstallData);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-
-        return PackageManager.INSTALL_SUCCEEDED;
-    }
-
-    static void onRestoreComplete(int returnCode, Context context, IntentSender target) {
-        Intent fillIn = new Intent();
-        fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
-                PackageManager.installStatusToPublicStatus(returnCode));
-        try {
-            target.sendIntent(context, 0, fillIn, null, null);
-        } catch (SendIntentException ignored) {
-        }
+        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(
+                this, mAppDataHelper);
+        return installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+                installReason, whiteListedPermissions, null);
     }
 
     static void setInstantAppForUser(PackageManagerServiceInjector injector,
@@ -8231,7 +6841,7 @@
         final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
         final long callingId = Binder.clearCallingIdentity();
         try {
-            final String activeLauncherPackageName = mDefaultAppProvider.getDefaultHome(userId);
+            final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
             final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
@@ -8570,144 +7180,6 @@
         }
     }
 
-    /** @param data Post-install is performed only if this is non-null. */
-    void restoreAndPostInstall(
-            int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
-        if (DEBUG_INSTALL) {
-            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.mPkg);
-        }
-
-        // A restore should be requested at this point if (a) the install
-        // succeeded, (b) the operation is not an update.
-        final boolean update = res.mRemovedInfo != null
-                && res.mRemovedInfo.mRemovedPackage != null;
-        boolean doRestore = !update && res.mPkg != null;
-
-        // Set up the post-install work request bookkeeping.  This will be used
-        // and cleaned up by the post-install event handling regardless of whether
-        // there's a restore pass performed.  Token values are >= 1.
-        int token;
-        if (mNextInstallToken < 0) mNextInstallToken = 1;
-        token = mNextInstallToken++;
-        if (data != null) {
-            mRunningInstalls.put(token, data);
-        } else if (DEBUG_INSTALL) {
-            Log.v(TAG, "No post-install required for " + token);
-        }
-
-        if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
-
-        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
-            // Pass responsibility to the Backup Manager.  It will perform a
-            // restore if appropriate, then pass responsibility back to the
-            // Package Manager to run the post-install observer callbacks
-            // and broadcasts.
-            if (res.mFreezer != null) {
-                res.mFreezer.close();
-            }
-            doRestore = performBackupManagerRestore(userId, token, res);
-        }
-
-        // If this is an update to a package that might be potentially downgraded, then we
-        // need to check with the rollback manager whether there's any userdata that might
-        // need to be snapshotted or restored for the package.
-        //
-        // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
-        if (res.mReturnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
-            doRestore = performRollbackManagerRestore(userId, token, res, data);
-        }
-
-        if (!doRestore) {
-            // No restore possible, or the Backup Manager was mysteriously not
-            // available -- just fire the post-install work request directly.
-            if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
-
-            Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);
-
-            Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
-            mHandler.sendMessage(msg);
-        }
-    }
-
-    /**
-     * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
-     * Returns whether the restore successfully completed.
-     */
-    private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
-        IBackupManager bm = IBackupManager.Stub.asInterface(
-                ServiceManager.getService(Context.BACKUP_SERVICE));
-        if (bm != null) {
-            // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
-            // in the BackupManager. USER_ALL is used in compatibility tests.
-            if (userId == UserHandle.USER_ALL) {
-                userId = UserHandle.USER_SYSTEM;
-            }
-            if (DEBUG_INSTALL) {
-                Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
-            }
-            Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
-            try {
-                if (bm.isUserReadyForBackup(userId)) {
-                    bm.restoreAtInstallForUser(
-                            userId, res.mPkg.getPackageName(), token);
-                } else {
-                    Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
-                            + "didn't take place.");
-                    return false;
-                }
-            } catch (RemoteException e) {
-                // can't happen; the backup manager is local
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception trying to enqueue restore", e);
-                return false;
-            }
-        } else {
-            Slog.e(TAG, "Backup Manager not found!");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
-     * Returns whether the restore successfully completed.
-     */
-    private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
-            PostInstallData data) {
-        RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
-
-        final String packageName = res.mPkg.getPackageName();
-        final int[] allUsers = mUserManager.getUserIds();
-        final int[] installedUsers;
-
-        final PackageSetting ps;
-        int appId = -1;
-        long ceDataInode = -1;
-        synchronized (mLock) {
-            ps = mSettings.getPackageLPr(packageName);
-            if (ps != null) {
-                appId = ps.getAppId();
-                ceDataInode = ps.getCeDataInode(userId);
-            }
-
-            // NOTE: We ignore the user specified in the InstallParam because we know this is
-            // an update, and hence need to restore data for all installed users.
-            installedUsers = ps.queryInstalledUsers(allUsers, true);
-        }
-
-        boolean doSnapshotOrRestore = data != null && data.args != null
-                && ((data.args.mInstallFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
-                || (data.args.mInstallFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
-
-        if (ps != null && doSnapshotOrRestore) {
-            final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
-            rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
-                    appId, ceDataInode, seInfo, token);
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Callback from PackageSettings whenever an app is first transitioned out of the
      * 'stopped' state.  Normally we just issue the broadcast, but we can't do that if
@@ -8756,343 +7228,7 @@
         });
     }
 
-    /**
-     * Create args that describe an existing installed package. Typically used
-     * when cleaning up old installs, or used as a move source.
-     */
-    InstallArgs createInstallArgsForExisting(String codePath, String[] instructionSets) {
-        return new FileInstallArgs(codePath, instructionSets, this);
-    }
-
-    @GuardedBy("mLock")
-    Map<String, ReconciledPackage> reconcilePackagesLocked(
-            final ReconcileRequest request, KeySetManagerService ksms,
-            PackageManagerServiceInjector injector)
-            throws ReconcileFailure {
-        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
-        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
-
-        // make a copy of the existing set of packages so we can combine them with incoming packages
-        final ArrayMap<String, AndroidPackage> combinedPackages =
-                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
-
-        combinedPackages.putAll(request.mAllPackages);
-
-        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
-                new ArrayMap<>();
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-
-            // add / replace existing with incoming packages
-            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
-                    scanResult.mRequest.mParsedPackage);
-
-            // in the first pass, we'll build up the set of incoming shared libraries
-            final List<SharedLibraryInfo> allowedSharedLibInfos =
-                    getAllowedSharedLibInfos(scanResult, request.mSharedLibrarySource);
-            final SharedLibraryInfo staticLib = scanResult.mStaticSharedLibraryInfo;
-            if (allowedSharedLibInfos != null) {
-                for (SharedLibraryInfo info : allowedSharedLibInfos) {
-                    if (!addSharedLibraryToPackageVersionMap(incomingSharedLibraries, info)) {
-                        throw new ReconcileFailure("Static Shared Library " + staticLib.getName()
-                                + " is being installed twice in this set!");
-                    }
-                }
-            }
-
-            // the following may be null if we're just reconciling on boot (and not during install)
-            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
-            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
-            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
-            final boolean isInstall = installArgs != null;
-            if (isInstall && (res == null || prepareResult == null)) {
-                throw new ReconcileFailure("Reconcile arguments are not balanced for "
-                        + installPackageName + "!");
-            }
-
-            final DeletePackageAction deletePackageAction;
-            // we only want to try to delete for non system apps
-            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
-                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
-                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
-                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
-                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
-                        deleteFlags, null /* all users */);
-                if (deletePackageAction == null) {
-                    throw new ReconcileFailure(
-                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
-                            "May not delete " + installPackageName + " to replace");
-                }
-            } else {
-                deletePackageAction = null;
-            }
-
-            final int scanFlags = scanResult.mRequest.mScanFlags;
-            final int parseFlags = scanResult.mRequest.mParseFlags;
-            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
-            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
-            final PackageSetting lastStaticSharedLibSetting =
-                    request.mLastStaticSharedLibSettings.get(installPackageName);
-            final PackageSetting signatureCheckPs =
-                    (prepareResult != null && lastStaticSharedLibSetting != null)
-                            ? lastStaticSharedLibSetting
-                            : scanResult.mPkgSetting;
-            boolean removeAppKeySetData = false;
-            boolean sharedUserSignaturesChanged = false;
-            SigningDetails signingDetails = null;
-            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
-                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                } else {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
-                                        + " upgrade keys do not match the previously installed"
-                                        + " version");
-                    } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
-                                + " signature changed; retaining data.";
-                        reportSettingsProblem(Log.WARN, msg);
-                    }
-                }
-                signingDetails = parsedPackage.getSigningDetails();
-            } else {
-                try {
-                    final VersionInfo versionInfo = request.mVersionInfos.get(installPackageName);
-                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
-                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
-                    final boolean isRollback = installArgs != null
-                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
-                    final boolean compatMatch = verifySignatures(signatureCheckPs,
-                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
-                            compareRecover, isRollback);
-                    // The new KeySets will be re-added later in the scanning process.
-                    if (compatMatch) {
-                        removeAppKeySetData = true;
-                    }
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
-                    // details
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        // Attempt to merge the existing lineage for the shared SigningDetails with
-                        // the lineage of the new package; if the shared SigningDetails are not
-                        // returned this indicates the new package added new signers to the lineage
-                        // and/or changed the capabilities of existing signers in the lineage.
-                        SigningDetails sharedSigningDetails =
-                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
-                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
-                                signingDetails);
-                        if (mergedDetails != sharedSigningDetails) {
-                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                    mergedDetails;
-                        }
-                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
-                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
-                        }
-                    }
-                } catch (PackageManagerException e) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(e);
-                    }
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // If the system app is part of a shared user we allow that shared user to
-                    // change
-                    // signatures as well as part of an OTA. We still need to verify that the
-                    // signatures
-                    // are consistent within the shared user for a given boot, so only allow
-                    // updating
-                    // the signatures on the first package scanned for the shared user (i.e. if the
-                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
-                                .signatures.mSigningDetails.getSignatures();
-                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
-                                && compareSignatures(sharedUserSignatures,
-                                parsedPackage.getSigningDetails().getSignatures())
-                                        != PackageManager.SIGNATURE_MATCH) {
-                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
-                                // Mismatched signatures is an error and silently skipping system
-                                // packages will likely break the device in unforeseen ways.
-                                // However, we allow the device to boot anyway because, prior to Q,
-                                // vendors were not expecting the platform to crash in this
-                                // situation.
-                                // This WILL be a hard failure on any new API levels after Q.
-                                throw new ReconcileFailure(
-                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                                        "Signature mismatch for shared user: "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            } else {
-                                // Treat mismatched signatures on system packages using a shared
-                                // UID as
-                                // fatal for the system overall, rather than just failing to install
-                                // whichever package happened to be scanned later.
-                                throw new IllegalStateException(
-                                        "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
-                                                + " for shared user "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            }
-                        }
-
-                        sharedUserSignaturesChanged = true;
-                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                parsedPackage.getSigningDetails();
-                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
-                    }
-                    // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
-                            + " signature changed; retaining data.";
-                    reportSettingsProblem(Log.WARN, msg);
-                } catch (IllegalArgumentException e) {
-                    // should never happen: certs matched when checking, but not when comparing
-                    // old to new for sharedUser
-                    throw new RuntimeException(
-                            "Signing certificates comparison made on incomparable signing details"
-                                    + " but somehow passed verifySignatures!", e);
-                }
-            }
-
-            result.put(installPackageName,
-                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
-                            res, request.mPreparedPackages.get(installPackageName), scanResult,
-                            deletePackageAction, allowedSharedLibInfos, signingDetails,
-                            sharedUserSignaturesChanged, removeAppKeySetData));
-        }
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            // Check all shared libraries and map to their actual file path.
-            // We only do this here for apps not on a system dir, because those
-            // are the only ones that can fail an install due to this.  We
-            // will take care of the system apps by updating all of their
-            // library paths after the scan is done. Also during the initial
-            // scan don't update any libs as we do this wholesale after all
-            // apps are scanned to avoid dependency based scanning.
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
-                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
-                    != 0) {
-                continue;
-            }
-            try {
-                result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        collectSharedLibraryInfos(scanResult.mRequest.mParsedPackage,
-                                combinedPackages, request.mSharedLibrarySource,
-                                incomingSharedLibraries, injector.getCompatibility());
-
-            } catch (PackageManagerException e) {
-                throw new ReconcileFailure(e.error, e.getMessage());
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Compare the newly scanned package with current system state to see which of its declared
-     * shared libraries should be allowed to be added to the system.
-     */
-    private static List<SharedLibraryInfo> getAllowedSharedLibInfos(
-            ScanResult scanResult,
-            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
-        // Let's used the parsed package as scanResult.pkgSetting may be null
-        final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-        if (scanResult.mStaticSharedLibraryInfo == null
-                && scanResult.mDynamicSharedLibraryInfos == null) {
-            return null;
-        }
-
-        // Any app can add new static shared libraries
-        if (scanResult.mStaticSharedLibraryInfo != null) {
-            return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
-        }
-        final boolean hasDynamicLibraries = parsedPackage.isSystem()
-                        && scanResult.mDynamicSharedLibraryInfos != null;
-        if (!hasDynamicLibraries) {
-            return null;
-        }
-        final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
-                .isUpdatedSystemApp();
-        // We may not yet have disabled the updated package yet, so be sure to grab the
-        // current setting if that's the case.
-        final PackageSetting updatedSystemPs = isUpdatedSystemApp
-                ? scanResult.mRequest.mDisabledPkgSetting == null
-                        ? scanResult.mRequest.mOldPkgSetting
-                        : scanResult.mRequest.mDisabledPkgSetting
-                : null;
-        if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
-                || updatedSystemPs.getPkg().getLibraryNames() == null)) {
-            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                    + " declares libraries that are not declared on the system image; skipping");
-            return null;
-        }
-        final ArrayList<SharedLibraryInfo> infos =
-                new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
-        for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
-            final String name = info.getName();
-            if (isUpdatedSystemApp) {
-                // New library entries can only be added through the
-                // system image.  This is important to get rid of a lot
-                // of nasty edge cases: for example if we allowed a non-
-                // system update of the app to add a library, then uninstalling
-                // the update would make the library go away, and assumptions
-                // we made such as through app install filtering would now
-                // have allowed apps on the device which aren't compatible
-                // with it.  Better to just have the restriction here, be
-                // conservative, and create many fewer cases that can negatively
-                // impact the user experience.
-                if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
-                    Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                            + " declares library " + name
-                            + " that is not declared on system image; skipping");
-                    continue;
-                }
-            }
-            if (sharedLibExists(
-                    name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
-                Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
-                        + name + " that already exists; skipping");
-                continue;
-            }
-            infos.add(info);
-        }
-        return infos;
-    }
-
-    /**
-     * Returns false if the adding shared library already exists in the map and so could not be
-     * added.
-     */
-    private static boolean addSharedLibraryToPackageVersionMap(
-            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
-            SharedLibraryInfo library) {
-        final String name = library.getName();
-        if (target.containsKey(name)) {
-            if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
-                // We've already added this non-version-specific library to the map.
-                return false;
-            } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
-                // We've already added this version of a version-specific library to the map.
-                return false;
-            }
-        } else {
-            target.put(name, new WatchedLongSparseArray<>());
-        }
-        target.get(name).put(library.getLongVersion(), library);
-        return true;
-    }
-
-    @Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
+    private @Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
             int userId) {
         final PackageSetting ps;
         synchronized (mLock) {
@@ -9155,34 +7291,8 @@
     @Override
     public void deleteExistingPackageAsUser(VersionedPackage versionedPackage,
             final IPackageDeleteObserver2 observer, final int userId) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.DELETE_PACKAGES, null);
-        Preconditions.checkNotNull(versionedPackage);
-        Preconditions.checkNotNull(observer);
-        final String packageName = versionedPackage.getPackageName();
-        final long versionCode = versionedPackage.getLongVersionCode();
-
-        int installedForUsersCount = 0;
-        synchronized (mLock) {
-            // Normalize package name to handle renamed packages and static libs
-            final String internalPkgName = resolveInternalPackageNameLPr(packageName, versionCode);
-            final PackageSetting ps = mSettings.getPackageLPr(internalPkgName);
-            if (ps != null) {
-                int[] installedUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
-                installedForUsersCount = installedUsers.length;
-            }
-        }
-
-        if (installedForUsersCount > 1) {
-            mDeletePackageHelper.deletePackageVersionedInternal(
-                    versionedPackage, observer, userId, 0, true);
-        } else {
-            try {
-                observer.onPackageDeleted(packageName, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
-                        null);
-            } catch (RemoteException re) {
-            }
-        }
+        mDeletePackageHelper.deleteExistingPackageAsUser(
+                versionedPackage, observer, userId);
     }
 
     @Override
@@ -9266,10 +7376,6 @@
         return mDevicePolicyManager;
     }
 
-    boolean shouldKeepUninstalledPackageLPr(String packageName) {
-        return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
-    }
-
     private static @Nullable ScanPartition resolveApexToScanPartition(
             ApexManager.ActiveApexInfo apexInfo) {
         for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
@@ -9458,10 +7564,6 @@
         return true;
     }
 
-    private void resetNetworkPolicies(int userId) {
-        mInjector.getLocalService(NetworkPolicyManagerInternal.class).resetUserState(userId);
-    }
-
     /**
      * Remove entries from the keystore daemon. Will only remove it if the
      * {@code appId} is valid.
@@ -9561,7 +7663,7 @@
     }
 
     @GuardedBy("mLock")
-    private int getUidTargetSdkVersionLockedLPr(int uid) {
+    int getUidTargetSdkVersionLockedLPr(int uid) {
         final int appId = UserHandle.getAppId(uid);
         final Object obj = mSettings.getSettingLPr(appId);
         if (obj instanceof SharedUserSetting) {
@@ -9597,57 +7699,11 @@
     @Override
     public void addPreferredActivity(IntentFilter filter, int match,
             ComponentName[] set, ComponentName activity, int userId, boolean removeExisting) {
-        addPreferredActivity(new WatchedIntentFilter(filter), match, set, activity, true, userId,
+        mPreferredActivityHelper.addPreferredActivity(
+                new WatchedIntentFilter(filter), match, set, activity, true, userId,
                 "Adding preferred", removeExisting);
     }
 
-    /**
-     * Variant that takes a {@link WatchedIntentFilter}
-     */
-    public void addPreferredActivity(WatchedIntentFilter filter, int match,
-            ComponentName[] set, ComponentName activity, boolean always, int userId,
-            String opname, boolean removeExisting) {
-        // writer
-        int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                false /* checkShell */, "add preferred activity");
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            synchronized (mLock) {
-                if (getUidTargetSdkVersionLockedLPr(callingUid)
-                        < Build.VERSION_CODES.FROYO) {
-                    Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
-                            + callingUid);
-                    return;
-                }
-            }
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-        }
-        if (filter.countActions() == 0) {
-            Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
-            return;
-        }
-        if (DEBUG_PREFERRED) {
-            Slog.i(TAG, opname + " activity " + activity.flattenToShortString() + " for user "
-                    + userId + ":");
-            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
-        }
-        synchronized (mLock) {
-            final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId);
-            final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
-            if (removeExisting && existing != null) {
-                Settings.removeFilters(pir, filter, existing);
-            }
-            pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
-            scheduleWritePackageRestrictionsLocked(userId);
-        }
-        if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
-            postPreferredActivityChangedBroadcast(userId);
-        }
-    }
-
     void postPreferredActivityChangedBroadcast(int userId) {
         mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId));
     }
@@ -9655,139 +7711,15 @@
     @Override
     public void replacePreferredActivity(IntentFilter filter, int match,
             ComponentName[] set, ComponentName activity, int userId) {
-        replacePreferredActivity(new WatchedIntentFilter(filter), match,
+        mPreferredActivityHelper.replacePreferredActivity(new WatchedIntentFilter(filter), match,
                                  set, activity, userId);
     }
 
-    /**
-     * Variant that takes a {@link WatchedIntentFilter}
-     */
-    public void replacePreferredActivity(WatchedIntentFilter filter, int match,
-            ComponentName[] set, ComponentName activity, int userId) {
-        if (filter.countActions() != 1) {
-            throw new IllegalArgumentException(
-                    "replacePreferredActivity expects filter to have only 1 action.");
-        }
-        if (filter.countDataAuthorities() != 0
-                || filter.countDataPaths() != 0
-                || filter.countDataSchemes() > 1
-                || filter.countDataTypes() != 0) {
-            throw new IllegalArgumentException(
-                    "replacePreferredActivity expects filter to have no data authorities, " +
-                    "paths, or types; and at most one scheme.");
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
-                false /* checkShell */, "replace preferred activity");
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            synchronized (mLock) {
-                if (getUidTargetSdkVersionLockedLPr(callingUid)
-                        < Build.VERSION_CODES.FROYO) {
-                    Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
-                            + Binder.getCallingUid());
-                    return;
-                }
-            }
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-        }
-
-        synchronized (mLock) {
-            final PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
-            if (pir != null) {
-                // Get all of the existing entries that exactly match this filter.
-                final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
-                if (existing != null && existing.size() == 1) {
-                    final PreferredActivity cur = existing.get(0);
-                    if (DEBUG_PREFERRED) {
-                        Slog.i(TAG, "Checking replace of preferred:");
-                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
-                        if (!cur.mPref.mAlways) {
-                            Slog.i(TAG, "  -- CUR; not mAlways!");
-                        } else {
-                            Slog.i(TAG, "  -- CUR: mMatch=" + cur.mPref.mMatch);
-                            Slog.i(TAG, "  -- CUR: mSet="
-                                    + Arrays.toString(cur.mPref.mSetComponents));
-                            Slog.i(TAG, "  -- CUR: mComponent=" + cur.mPref.mShortComponent);
-                            Slog.i(TAG, "  -- NEW: mMatch="
-                                    + (match&IntentFilter.MATCH_CATEGORY_MASK));
-                            Slog.i(TAG, "  -- CUR: mSet=" + Arrays.toString(set));
-                            Slog.i(TAG, "  -- CUR: mComponent=" + activity.flattenToShortString());
-                        }
-                    }
-                    if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity)
-                            && cur.mPref.mMatch == (match&IntentFilter.MATCH_CATEGORY_MASK)
-                            && cur.mPref.sameSet(set)) {
-                        // Setting the preferred activity to what it happens to be already
-                        if (DEBUG_PREFERRED) {
-                            Slog.i(TAG, "Replacing with same preferred activity "
-                                    + cur.mPref.mShortComponent + " for user "
-                                    + userId + ":");
-                            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
-                        }
-                        return;
-                    }
-                }
-                if (existing != null) {
-                    Settings.removeFilters(pir, filter, existing);
-                }
-            }
-        }
-        addPreferredActivity(filter, match, set, activity, true, userId,
-                "Replacing preferred", false);
-    }
-
     @Override
     public void clearPackagePreferredActivities(String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return;
-        }
-        // writer
-        synchronized (mLock) {
-            AndroidPackage pkg = mPackages.get(packageName);
-            if (pkg == null || !isCallerSameApp(packageName, callingUid)) {
-                if (mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    if (getUidTargetSdkVersionLockedLPr(callingUid)
-                            < Build.VERSION_CODES.FROYO) {
-                        Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid "
-                                + callingUid);
-                        return;
-                    }
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-                }
-            }
-            final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (ps != null
-                    && shouldFilterApplicationLocked(
-                            ps, callingUid, UserHandle.getUserId(callingUid))) {
-                return;
-            }
-        }
-        int callingUserId = UserHandle.getCallingUserId();
-        clearPackagePreferredActivities(packageName, callingUserId);
+        mPreferredActivityHelper.clearPackagePreferredActivities(packageName);
     }
 
-    /** This method takes a specific user id as well as UserHandle.USER_ALL. */
-    void clearPackagePreferredActivities(String packageName, int userId) {
-        final SparseBooleanArray changedUsers = new SparseBooleanArray();
-        synchronized (mLock) {
-            clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
-        }
-        if (changedUsers.size() > 0) {
-            updateDefaultHomeNotLocked(changedUsers);
-            postPreferredActivityChangedBroadcast(userId);
-            synchronized (mLock) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        }
-    }
 
     /** This method takes a specific user id as well as UserHandle.USER_ALL. */
     @GuardedBy("mLock")
@@ -9804,185 +7736,31 @@
 
         // Persistent preferred activity might have came into effect due to this
         // install.
-        updateDefaultHomeNotLocked(userId);
+        mPreferredActivityHelper.updateDefaultHomeNotLocked(userId);
     }
 
     @Override
     public void resetApplicationPreferences(int userId) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-        final long identity = Binder.clearCallingIdentity();
-        // writer
-        try {
-            final SparseBooleanArray changedUsers = new SparseBooleanArray();
-            synchronized (mLock) {
-                clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
-            }
-            if (changedUsers.size() > 0) {
-                postPreferredActivityChangedBroadcast(userId);
-            }
-            synchronized (mLock) {
-                mSettings.applyDefaultPreferredAppsLPw(userId);
-                mDomainVerificationManager.clearUser(userId);
-                final int numPackages = mPackages.size();
-                for (int i = 0; i < numPackages; i++) {
-                    final AndroidPackage pkg = mPackages.valueAt(i);
-                    mPermissionManager.resetRuntimePermissions(pkg, userId);
-                }
-            }
-            updateDefaultHomeNotLocked(userId);
-            resetNetworkPolicies(userId);
-            synchronized (mLock) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        mPreferredActivityHelper.resetApplicationPreferences(userId);
     }
 
     @Override
     public int getPreferredActivities(List<IntentFilter> outFilters,
             List<ComponentName> outActivities, String packageName) {
-        List<WatchedIntentFilter> temp =
-                WatchedIntentFilter.toWatchedIntentFilterList(outFilters);
-        final int result = getPreferredActivitiesInternal(
-                temp, outActivities, packageName);
-        outFilters.clear();
-        for (int i = 0; i < temp.size(); i++) {
-            outFilters.add(temp.get(i).getIntentFilter());
-        }
-        return result;
-    }
-
-    /**
-     * Variant that takes a {@link WatchedIntentFilter}
-     */
-    public int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters,
-            List<ComponentName> outActivities, String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return 0;
-        }
-        int num = 0;
-        final int userId = UserHandle.getCallingUserId();
-        // reader
-        synchronized (mLock) {
-            PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
-            if (pir != null) {
-                final Iterator<PreferredActivity> it = pir.filterIterator();
-                while (it.hasNext()) {
-                    final PreferredActivity pa = it.next();
-                    final String prefPackageName = pa.mPref.mComponent.getPackageName();
-                    if (packageName == null
-                            || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
-                        if (shouldFilterApplicationLocked(
-                                mSettings.getPackageLPr(prefPackageName), callingUid, userId)) {
-                            continue;
-                        }
-                        if (outFilters != null) {
-                            outFilters.add(new WatchedIntentFilter(pa.getIntentFilter()));
-                        }
-                        if (outActivities != null) {
-                            outActivities.add(pa.mPref.mComponent);
-                        }
-                    }
-                }
-            }
-        }
-
-        return num;
+        return mPreferredActivityHelper.getPreferredActivities(outFilters, outActivities,
+                packageName);
     }
 
     @Override
     public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
             int userId) {
-        addPersistentPreferredActivity(new WatchedIntentFilter(filter), activity, userId);
-    }
-
-    /**
-     * Variant that takes a {@link WatchedIntentFilter}
-     */
-    public void addPersistentPreferredActivity(WatchedIntentFilter filter, ComponentName activity,
-            int userId) {
-        int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID) {
-            throw new SecurityException(
-                    "addPersistentPreferredActivity can only be run by the system");
-        }
-        if (filter.countActions() == 0) {
-            Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
-            return;
-        }
-        if (DEBUG_PREFERRED) {
-            Slog.i(TAG, "Adding persistent preferred activity " + activity
-                    + " for user " + userId + ":");
-            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
-        }
-        synchronized (mLock) {
-            mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
-                    new PersistentPreferredActivity(filter, activity, true));
-            scheduleWritePackageRestrictionsLocked(userId);
-        }
-        if (isHomeFilter(filter)) {
-            updateDefaultHomeNotLocked(userId);
-        }
-        postPreferredActivityChangedBroadcast(userId);
+        mPreferredActivityHelper.addPersistentPreferredActivity(new WatchedIntentFilter(filter),
+                activity, userId);
     }
 
     @Override
     public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
-        int callingUid = Binder.getCallingUid();
-        if (callingUid != Process.SYSTEM_UID) {
-            throw new SecurityException(
-                    "clearPackagePersistentPreferredActivities can only be run by the system");
-        }
-        boolean changed = false;
-        synchronized (mLock) {
-            changed = mSettings.clearPackagePersistentPreferredActivities(packageName, userId);
-        }
-        if (changed) {
-            updateDefaultHomeNotLocked(userId);
-            postPreferredActivityChangedBroadcast(userId);
-            synchronized (mLock) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        }
-    }
-
-    /**
-     * Common machinery for picking apart a restored XML blob and passing
-     * it to a caller-supplied functor to be applied to the running system.
-     */
-    private void restoreFromXml(TypedXmlPullParser parser, int userId,
-            String expectedStartTag, BlobXmlRestorer functor)
-            throws IOException, XmlPullParserException {
-        int type;
-        while ((type = parser.next()) != XmlPullParser.START_TAG
-                && type != XmlPullParser.END_DOCUMENT) {
-        }
-        if (type != XmlPullParser.START_TAG) {
-            // oops didn't find a start tag?!
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Didn't find start tag during restore");
-            }
-            return;
-        }
-        // this is supposed to be TAG_PREFERRED_BACKUP
-        if (!expectedStartTag.equals(parser.getName())) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Found unexpected tag " + parser.getName());
-            }
-            return;
-        }
-
-        // skip interfering stuff, then we're aligned with the backing implementation
-        while ((type = parser.next()) == XmlPullParser.TEXT) { }
-        functor.apply(parser, userId);
-    }
-
-    private interface BlobXmlRestorer {
-        void apply(TypedXmlPullParser parser, int userId)
-                throws IOException, XmlPullParserException;
+        mPreferredActivityHelper.clearPackagePersistentPreferredActivities(packageName, userId);
     }
 
     /**
@@ -9992,55 +7770,12 @@
      */
     @Override
     public byte[] getPreferredActivityBackup(int userId) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only the system may call getPreferredActivityBackup()");
-        }
-
-        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
-        try {
-            final TypedXmlSerializer serializer = Xml.newFastSerializer();
-            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
-            serializer.startDocument(null, true);
-            serializer.startTag(null, TAG_PREFERRED_BACKUP);
-
-            synchronized (mLock) {
-                mSettings.writePreferredActivitiesLPr(serializer, userId, true);
-            }
-
-            serializer.endTag(null, TAG_PREFERRED_BACKUP);
-            serializer.endDocument();
-            serializer.flush();
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Unable to write preferred activities for backup", e);
-            }
-            return null;
-        }
-
-        return dataStream.toByteArray();
+        return mPreferredActivityHelper.getPreferredActivityBackup(userId);
     }
 
     @Override
     public void restorePreferredActivities(byte[] backup, int userId) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only the system may call restorePreferredActivities()");
-        }
-
-        try {
-            final TypedXmlPullParser parser = Xml.newFastPullParser();
-            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
-            restoreFromXml(parser, userId, TAG_PREFERRED_BACKUP,
-                    (readParser, readUserId) -> {
-                        synchronized (mLock) {
-                            mSettings.readPreferredActivitiesLPw(readParser, readUserId);
-                        }
-                        updateDefaultHomeNotLocked(readUserId);
-                    });
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
-            }
-        }
+        mPreferredActivityHelper.restorePreferredActivities(backup, userId);
     }
 
     /**
@@ -10050,59 +7785,12 @@
      */
     @Override
     public byte[] getDefaultAppsBackup(int userId) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only the system may call getDefaultAppsBackup()");
-        }
-
-        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
-        try {
-            final TypedXmlSerializer serializer = Xml.newFastSerializer();
-            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
-            serializer.startDocument(null, true);
-            serializer.startTag(null, TAG_DEFAULT_APPS);
-
-            synchronized (mLock) {
-                mSettings.writeDefaultAppsLPr(serializer, userId);
-            }
-
-            serializer.endTag(null, TAG_DEFAULT_APPS);
-            serializer.endDocument();
-            serializer.flush();
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Unable to write default apps for backup", e);
-            }
-            return null;
-        }
-
-        return dataStream.toByteArray();
+        return mPreferredActivityHelper.getDefaultAppsBackup(userId);
     }
 
     @Override
     public void restoreDefaultApps(byte[] backup, int userId) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only the system may call restoreDefaultApps()");
-        }
-
-        try {
-            final TypedXmlPullParser parser = Xml.newFastPullParser();
-            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
-            restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
-                    (parser1, userId1) -> {
-                        final String defaultBrowser;
-                        synchronized (mLock) {
-                            mSettings.readDefaultAppsLPw(parser1, userId1);
-                            defaultBrowser = mSettings.removeDefaultBrowserPackageNameLPw(userId1);
-                        }
-                        if (defaultBrowser != null) {
-                            mDefaultAppProvider.setDefaultBrowser(defaultBrowser, false, userId1);
-                        }
-                    });
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Exception restoring default apps: " + e.getMessage());
-            }
-        }
+        mPreferredActivityHelper.restoreDefaultApps(backup, userId);
     }
 
     @Override
@@ -10257,114 +7945,19 @@
         return mComputer.getDefaultHomeActivity(userId);
     }
 
-    private Intent getHomeIntent() {
+    Intent getHomeIntent() {
         return mComputer.getHomeIntent();
     }
 
-    private WatchedIntentFilter getHomeFilter() {
-        WatchedIntentFilter filter = new WatchedIntentFilter(Intent.ACTION_MAIN);
-        filter.addCategory(Intent.CATEGORY_HOME);
-        filter.addCategory(Intent.CATEGORY_DEFAULT);
-        return filter;
-    }
-
-    private boolean isHomeFilter(@NonNull WatchedIntentFilter filter) {
-        return filter.hasAction(Intent.ACTION_MAIN) && filter.hasCategory(Intent.CATEGORY_HOME)
-                && filter.hasCategory(CATEGORY_DEFAULT);
-    }
-
     ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
             int userId) {
         return mComputer.getHomeActivitiesAsUser(allHomeCandidates,
                 userId);
     }
 
-    /** <b>must not hold {@link #mLock}</b> */
-    void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
-        if (Thread.holdsLock(mLock)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mLock", new Throwable());
-        }
-        for (int i = userIds.size() - 1; i >= 0; --i) {
-            final int userId = userIds.keyAt(i);
-            updateDefaultHomeNotLocked(userId);
-        }
-    }
-
-    /**
-     * <b>must not hold {@link #mLock}</b>
-     *
-     * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
-     */
-    private boolean updateDefaultHomeNotLocked(int userId) {
-        if (Thread.holdsLock(mLock)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mLock", new Throwable());
-        }
-        if (!mSystemReady) {
-            // We might get called before system is ready because of package changes etc, but
-            // finding preferred activity depends on settings provider, so we ignore the update
-            // before that.
-            return false;
-        }
-        final Intent intent = getHomeIntent();
-        final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
-                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
-        final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
-                intent, null, 0, resolveInfos, true, false, false, userId);
-        final String packageName = preferredResolveInfo != null
-                && preferredResolveInfo.activityInfo != null
-                ? preferredResolveInfo.activityInfo.packageName : null;
-        final String currentPackageName = mDefaultAppProvider.getDefaultHome(userId);
-        if (TextUtils.equals(currentPackageName, packageName)) {
-            return false;
-        }
-        final String[] callingPackages = getPackagesForUid(Binder.getCallingUid());
-        if (callingPackages != null && ArrayUtils.contains(callingPackages,
-                mRequiredPermissionControllerPackage)) {
-            // PermissionController manages default home directly.
-            return false;
-        }
-
-        if (packageName == null) {
-            // Keep the default home package in RoleManager.
-            return false;
-        }
-        return mDefaultAppProvider.setDefaultHome(packageName, userId, mContext.getMainExecutor(),
-                successful -> {
-                    if (successful) {
-                        postPreferredActivityChangedBroadcast(userId);
-                    }
-                });
-    }
-
     @Override
     public void setHomeActivity(ComponentName comp, int userId) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return;
-        }
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
-        getHomeActivitiesAsUser(homeActivities, userId);
-
-        boolean found = false;
-
-        final int size = homeActivities.size();
-        final ComponentName[] set = new ComponentName[size];
-        for (int i = 0; i < size; i++) {
-            final ResolveInfo candidate = homeActivities.get(i);
-            final ActivityInfo info = candidate.activityInfo;
-            final ComponentName activityName = new ComponentName(info.packageName, info.name);
-            set[i] = activityName;
-            if (!found && activityName.equals(comp)) {
-                found = true;
-            }
-        }
-        if (!found) {
-            throw new IllegalArgumentException("Component " + comp + " cannot be home on user "
-                    + userId);
-        }
-        replacePreferredActivity(getHomeFilter(), IntentFilter.MATCH_CATEGORY_EMPTY,
-                set, comp, userId);
+        mPreferredActivityHelper.setHomeActivity(comp, userId);
     }
 
     private @Nullable String getSetupWizardPackageNameImpl() {
@@ -11468,6 +9061,8 @@
                         mPerUidReadTimeoutsCache = null;
                     }
                 });
+
+        mBackgroundDexOptService.systemReady();
     }
 
     public void waitForAppDataPrepared() {
@@ -11502,632 +9097,22 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+        new DumpHelper(this).doDump(fd, pw, args);
+    }
 
-        DumpState dumpState = new DumpState();
-        ArraySet<String> permissionNames = null;
-
-        int opti = 0;
-        while (opti < args.length) {
-            String opt = args[opti];
-            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
-                break;
-            }
-            opti++;
-
-            if ("-a".equals(opt)) {
-                // Right now we only know how to print all.
-            } else if ("-h".equals(opt)) {
-                pw.println("Package manager dump options:");
-                pw.println("  [-h] [-f] [--checkin] [--all-components] [cmd] ...");
-                pw.println("    --checkin: dump for a checkin");
-                pw.println("    -f: print details of intent filters");
-                pw.println("    -h: print this help");
-                pw.println("    --all-components: include all component names in package dump");
-                pw.println("  cmd may be one of:");
-                pw.println("    apex: list active APEXes and APEX session state");
-                pw.println("    l[ibraries]: list known shared libraries");
-                pw.println("    f[eatures]: list device features");
-                pw.println("    k[eysets]: print known keysets");
-                pw.println("    r[esolvers] [activity|service|receiver|content]: dump intent resolvers");
-                pw.println("    perm[issions]: dump permissions");
-                pw.println("    permission [name ...]: dump declaration and use of given permission");
-                pw.println("    pref[erred]: print preferred package settings");
-                pw.println("    preferred-xml [--full]: print preferred package settings as xml");
-                pw.println("    prov[iders]: dump content providers");
-                pw.println("    p[ackages]: dump installed packages");
-                pw.println("    q[ueries]: dump app queryability calculations");
-                pw.println("    s[hared-users]: dump shared user IDs");
-                pw.println("    m[essages]: print collected runtime messages");
-                pw.println("    v[erifiers]: print package verifier info");
-                pw.println("    d[omain-preferred-apps]: print domains preferred apps");
-                pw.println("    i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
-                pw.println("    t[imeouts]: print read timeouts for known digesters");
-                pw.println("    version: print database version info");
-                pw.println("    write: write current settings now");
-                pw.println("    installs: details about install sessions");
-                pw.println("    check-permission <permission> <package> [<user>]: does pkg hold perm?");
-                pw.println("    dexopt: dump dexopt state");
-                pw.println("    compiler-stats: dump compiler statistics");
-                pw.println("    service-permissions: dump permissions required by services");
-                pw.println("    snapshot: dump snapshot statistics");
-                pw.println("    protected-broadcasts: print list of protected broadcast actions");
-                pw.println("    known-packages: dump known packages");
-                pw.println("    <package.name>: info about given package");
-                return;
-            } else if ("--checkin".equals(opt)) {
-                dumpState.setCheckIn(true);
-            } else if ("--all-components".equals(opt)) {
-                dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS);
-            } else if ("-f".equals(opt)) {
-                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
-            } else if ("--proto".equals(opt)) {
-                dumpProto(fd);
-                return;
-            } else {
-                pw.println("Unknown argument: " + opt + "; use -h for help");
-            }
-        }
-
-        // Is the caller requesting to dump a particular piece of data?
-        if (opti < args.length) {
-            String cmd = args[opti];
-            opti++;
-            // Is this a package name?
-            if ("android".equals(cmd) || cmd.contains(".")) {
-                dumpState.setTargetPackageName(cmd);
-                // When dumping a single package, we always dump all of its
-                // filter information since the amount of data will be reasonable.
-                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
-            } else if ("check-permission".equals(cmd)) {
-                if (opti >= args.length) {
-                    pw.println("Error: check-permission missing permission argument");
-                    return;
-                }
-                String perm = args[opti];
-                opti++;
-                if (opti >= args.length) {
-                    pw.println("Error: check-permission missing package argument");
-                    return;
-                }
-
-                String pkg = args[opti];
-                opti++;
-                int user = UserHandle.getUserId(Binder.getCallingUid());
-                if (opti < args.length) {
-                    try {
-                        user = Integer.parseInt(args[opti]);
-                    } catch (NumberFormatException e) {
-                        pw.println("Error: check-permission user argument is not a number: "
-                                + args[opti]);
-                        return;
-                    }
-                }
-
-                // Normalize package name to handle renamed packages and static libs
-                pkg = resolveInternalPackageNameLPr(pkg, PackageManager.VERSION_CODE_HIGHEST);
-
-                pw.println(checkPermission(perm, pkg, user));
-                return;
-            } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_LIBS);
-            } else if ("f".equals(cmd) || "features".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_FEATURES);
-            } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
-                if (opti >= args.length) {
-                    dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS
-                            | DumpState.DUMP_SERVICE_RESOLVERS
-                            | DumpState.DUMP_RECEIVER_RESOLVERS
-                            | DumpState.DUMP_CONTENT_RESOLVERS);
-                } else {
-                    while (opti < args.length) {
-                        String name = args[opti];
-                        if ("a".equals(name) || "activity".equals(name)) {
-                            dumpState.setDump(DumpState.DUMP_ACTIVITY_RESOLVERS);
-                        } else if ("s".equals(name) || "service".equals(name)) {
-                            dumpState.setDump(DumpState.DUMP_SERVICE_RESOLVERS);
-                        } else if ("r".equals(name) || "receiver".equals(name)) {
-                            dumpState.setDump(DumpState.DUMP_RECEIVER_RESOLVERS);
-                        } else if ("c".equals(name) || "content".equals(name)) {
-                            dumpState.setDump(DumpState.DUMP_CONTENT_RESOLVERS);
-                        } else {
-                            pw.println("Error: unknown resolver table type: " + name);
-                            return;
-                        }
-                        opti++;
-                    }
-                }
-            } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PERMISSIONS);
-            } else if ("permission".equals(cmd)) {
-                if (opti >= args.length) {
-                    pw.println("Error: permission requires permission name");
-                    return;
-                }
-                permissionNames = new ArraySet<>();
-                while (opti < args.length) {
-                    permissionNames.add(args[opti]);
-                    opti++;
-                }
-                dumpState.setDump(DumpState.DUMP_PERMISSIONS
-                        | DumpState.DUMP_PACKAGES | DumpState.DUMP_SHARED_USERS);
-            } else if ("pref".equals(cmd) || "preferred".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PREFERRED);
-            } else if ("preferred-xml".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
-                if (opti < args.length && "--full".equals(args[opti])) {
-                    dumpState.setFullPreferred(true);
-                    opti++;
-                }
-            } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
-            } else if ("p".equals(cmd) || "packages".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PACKAGES);
-            } else if ("q".equals(cmd) || "queries".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_QUERIES);
-            } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_SHARED_USERS);
-                if (opti < args.length && "noperm".equals(args[opti])) {
-                    dumpState.setOptionEnabled(DumpState.OPTION_SKIP_PERMISSIONS);
-                }
-            } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PROVIDERS);
-            } else if ("m".equals(cmd) || "messages".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_MESSAGES);
-            } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_VERIFIERS);
-            } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
-            } else if ("version".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_VERSION);
-            } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_KEYSETS);
-            } else if ("installs".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_INSTALLS);
-            } else if ("frozen".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_FROZEN);
-            } else if ("volumes".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_VOLUMES);
-            } else if ("dexopt".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_DEXOPT);
-            } else if ("compiler-stats".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
-            } else if ("changes".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_CHANGES);
-            } else if ("service-permissions".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
-            } else if ("known-packages".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
-            } else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
-            } else if ("snapshot".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
-                if (opti < args.length) {
-                    if ("--full".equals(args[opti])) {
-                        dumpState.setBrief(false);
-                        opti++;
-                    } else if ("--brief".equals(args[opti])) {
-                        dumpState.setBrief(true);
-                        opti++;
-                    }
-                }
-            } else if ("protected-broadcasts".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_PROTECTED_BROADCASTS);
-            } else if ("write".equals(cmd)) {
-                synchronized (mLock) {
-                    writeSettingsLPrTEMP();
-                    pw.println("Settings written.");
-                    return;
+    void dumpSnapshotStats(PrintWriter pw, boolean isBrief) {
+        if (!mSnapshotEnabled) {
+            pw.println("  Snapshots disabled");
+        } else {
+            int hits = 0;
+            int level = sSnapshotCorked.get();
+            synchronized (mSnapshotLock) {
+                if (mSnapshotComputer != null) {
+                    hits = mSnapshotComputer.getUsed();
                 }
             }
-        }
-
-        final String packageName = dumpState.getTargetPackageName();
-        final boolean checkin = dumpState.isCheckIn();
-
-        // Return if the package doesn't exist.
-        if (packageName != null
-                && getPackageSetting(packageName) == null
-                && !mApexManager.isApexPackage(packageName)) {
-            pw.println("Unable to find package: " + packageName);
-            return;
-        }
-
-        if (checkin) {
-            pw.println("vers,1");
-        }
-
-        // reader
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_VERSION)
-                && packageName == null) {
-            dump(DumpState.DUMP_VERSION, fd, pw, dumpState);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-            ipw.println("Known Packages:");
-            ipw.increaseIndent();
-            for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) {
-                final String knownPackage = PackageManagerInternal.knownPackageToString(i);
-                ipw.print(knownPackage);
-                ipw.println(":");
-                final String[] pkgNames = mPmInternal.getKnownPackageNames(i,
-                        UserHandle.USER_SYSTEM);
-                ipw.increaseIndent();
-                if (ArrayUtils.isEmpty(pkgNames)) {
-                    ipw.println("none");
-                } else {
-                    for (String name : pkgNames) {
-                        ipw.println(name);
-                    }
-                }
-                ipw.decreaseIndent();
-            }
-            ipw.decreaseIndent();
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_VERIFIERS)
-                && packageName == null) {
-            final String requiredVerifierPackage = mRequiredVerifierPackage;
-            if (!checkin) {
-                if (dumpState.onTitlePrinted()) {
-                    pw.println();
-                }
-                pw.println("Verifiers:");
-                pw.print("  Required: ");
-                pw.print(requiredVerifierPackage);
-                pw.print(" (uid=");
-                pw.print(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
-                        UserHandle.USER_SYSTEM));
-                pw.println(")");
-            } else if (requiredVerifierPackage != null) {
-                pw.print("vrfy,"); pw.print(requiredVerifierPackage);
-                pw.print(",");
-                pw.println(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
-                        UserHandle.USER_SYSTEM));
-            }
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER)
-                && packageName == null) {
-            final DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
-            final ComponentName verifierComponent = proxy.getComponentName();
-            if (verifierComponent != null) {
-                String verifierPackageName = verifierComponent.getPackageName();
-                if (!checkin) {
-                    if (dumpState.onTitlePrinted()) {
-                        pw.println();
-                    }
-                    pw.println("Domain Verifier:");
-                    pw.print("  Using: ");
-                    pw.print(verifierPackageName);
-                    pw.print(" (uid=");
-                    pw.print(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
-                            UserHandle.USER_SYSTEM));
-                    pw.println(")");
-                } else if (verifierPackageName != null) {
-                    pw.print("dv,"); pw.print(verifierPackageName);
-                    pw.print(",");
-                    pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
-                            UserHandle.USER_SYSTEM));
-                }
-            } else {
-                pw.println();
-                pw.println("No Domain Verifier available!");
-            }
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_LIBS)
-                && packageName == null) {
-            dump(DumpState.DUMP_LIBS, fd, pw, dumpState);
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_FEATURES)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            if (!checkin) {
-                pw.println("Features:");
-            }
-
-            synchronized (mAvailableFeatures) {
-                for (FeatureInfo feat : mAvailableFeatures.values()) {
-                    if (!checkin) {
-                        pw.print("  ");
-                        pw.print(feat.name);
-                        if (feat.version > 0) {
-                            pw.print(" version=");
-                            pw.print(feat.version);
-                        }
-                        pw.println();
-                    } else {
-                        pw.print("feat,");
-                        pw.print(feat.name);
-                        pw.print(",");
-                        pw.println(feat.version);
-                    }
-                }
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) {
-            synchronized (mLock) {
-                mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName);
-            }
-        }
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) {
-            synchronized (mLock) {
-                mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName);
-            }
-        }
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) {
-            synchronized (mLock) {
-                mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName);
-            }
-        }
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) {
-            synchronized (mLock) {
-                mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
-            dump(DumpState.DUMP_PREFERRED, fd, pw, dumpState);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)
-                && packageName == null) {
-            dump(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
-            dump(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
-            synchronized (mLock) {
-                mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
-            synchronized (mLock) {
-                mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
-            synchronized (mLock) {
-                mSettings.getKeySetManagerService().dumpLPr(pw, packageName, dumpState);
-            }
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
-            // This cannot be moved to ComputerEngine since some variables of the collections
-            // in PackageUserState such as suspendParams, disabledComponents and enabledComponents
-            // do not have a copy.
-            synchronized (mLock) {
-                mSettings.dumpPackagesLPr(pw, packageName, permissionNames, dumpState, checkin);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_QUERIES)) {
-            dump(DumpState.DUMP_QUERIES, fd, pw, dumpState);
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
-            // This cannot be moved to ComputerEngine since the set of packages in the
-            // SharedUserSetting do not have a copy.
-            synchronized (mLock) {
-                mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_CHANGES)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            pw.println("Package Changes:");
-            synchronized (mLock) {
-                pw.print("  Sequence number="); pw.println(mChangedPackagesSequenceNumber);
-                final int K = mChangedPackages.size();
-                for (int i = 0; i < K; i++) {
-                    final SparseArray<String> changes = mChangedPackages.valueAt(i);
-                    pw.print("  User "); pw.print(mChangedPackages.keyAt(i)); pw.println(":");
-                    final int N = changes.size();
-                    if (N == 0) {
-                        pw.print("    "); pw.println("No packages changed");
-                    } else {
-                        for (int j = 0; j < N; j++) {
-                            final String pkgName = changes.valueAt(j);
-                            final int sequenceNumber = changes.keyAt(j);
-                            pw.print("    ");
-                            pw.print("seq=");
-                            pw.print(sequenceNumber);
-                            pw.print(", package=");
-                            pw.println(pkgName);
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_FROZEN)
-                && packageName == null) {
-            // XXX should handle packageName != null by dumping only install data that
-            // the given package is involved with.
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-            ipw.println();
-            ipw.println("Frozen packages:");
-            ipw.increaseIndent();
-            synchronized (mLock) {
-                if (mFrozenPackages.size() == 0) {
-                    ipw.println("(none)");
-                } else {
-                    for (int i = 0; i < mFrozenPackages.size(); i++) {
-                        ipw.println(mFrozenPackages.valueAt(i));
-                    }
-                }
-            }
-            ipw.decreaseIndent();
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_VOLUMES)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-            ipw.println();
-            ipw.println("Loaded volumes:");
-            ipw.increaseIndent();
-            synchronized (mLoadedVolumes) {
-                if (mLoadedVolumes.size() == 0) {
-                    ipw.println("(none)");
-                } else {
-                    for (int i = 0; i < mLoadedVolumes.size(); i++) {
-                        ipw.println(mLoadedVolumes.valueAt(i));
-                    }
-                }
-            }
-            ipw.decreaseIndent();
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS)
-                && packageName == null) {
-            synchronized (mLock) {
-                mComponentResolver.dumpServicePermissions(pw, dumpState);
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_DEXOPT)) {
-            dump(DumpState.DUMP_DEXOPT, fd, pw, dumpState);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) {
-            dump(DumpState.DUMP_COMPILER_STATS, fd, pw, dumpState);
-        }
-
-        if (dumpState.isDumping(DumpState.DUMP_MESSAGES)
-                && packageName == null) {
-            if (!checkin) {
-                if (dumpState.onTitlePrinted()) {
-                    pw.println();
-                }
-                synchronized (mLock) {
-                    mSettings.dumpReadMessagesLPr(pw, dumpState);
-                }
-                pw.println();
-                pw.println("Package warning messages:");
-                dumpCriticalInfo(pw, null);
-            } else {
-                dumpCriticalInfo(pw, "msg,");
-            }
-        }
-
-        // PackageInstaller should be called outside of mPackages lock
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_INSTALLS)
-                && packageName == null) {
-            // XXX should handle packageName != null by dumping only install data that
-            // the given package is involved with.
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            mInstallerService.dump(new IndentingPrintWriter(pw, "  ", 120));
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_APEX)
-                && (packageName == null || mApexManager.isApexPackage(packageName))) {
-            mApexManager.dump(pw, packageName);
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PER_UID_READ_TIMEOUTS)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            pw.println("Per UID read timeouts:");
-            pw.println("    Default timeouts flag: " + getDefaultTimeouts());
-            pw.println("    Known digesters list flag: " + getKnownDigestersList());
-
-            PerUidReadTimeouts[] items = getPerUidReadTimeouts();
-            pw.println("    Timeouts (" + items.length + "):");
-            for (PerUidReadTimeouts item : items) {
-                pw.print("        (");
-                pw.print("uid=" + item.uid + ", ");
-                pw.print("minTimeUs=" + item.minTimeUs + ", ");
-                pw.print("minPendingTimeUs=" + item.minPendingTimeUs + ", ");
-                pw.print("maxPendingTimeUs=" + item.maxPendingTimeUs);
-                pw.println(")");
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            pw.println("Snapshot statistics");
-            if (!mSnapshotEnabled) {
-                pw.println("  Snapshots disabled");
-            } else {
-                int hits = 0;
-                int level = sSnapshotCorked.get();
-                synchronized (mSnapshotLock) {
-                    if (mSnapshotComputer != null) {
-                        hits = mSnapshotComputer.getUsed();
-                    }
-                }
-                final long now = SystemClock.currentTimeMicro();
-                mSnapshotStatistics.dump(pw, "  ", now, hits, level, dumpState.isBrief());
-            }
-        }
-
-        if (!checkin
-                && dumpState.isDumping(DumpState.DUMP_PROTECTED_BROADCASTS)
-                && packageName == null) {
-            if (dumpState.onTitlePrinted()) {
-                pw.println();
-            }
-            pw.println("Protected broadcast actions:");
-            synchronized (mProtectedBroadcasts) {
-                for (int i = 0; i < mProtectedBroadcasts.size(); i++) {
-                    pw.print("  ");
-                    pw.println(mProtectedBroadcasts.valueAt(i));
-                }
-            }
-
+            final long now = SystemClock.currentTimeMicro();
+            mSnapshotStatistics.dump(pw, "  ", now, hits, level, isBrief);
         }
     }
 
@@ -12135,7 +9120,7 @@
      * Dump package manager states to the file according to a given dumping type of
      * {@link DumpState}.
      */
-    private void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
+    void dumpComputer(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) {
         mComputer.dump(type, fd, pw, dumpState);
     }
 
@@ -12160,83 +9145,6 @@
         }
     }
 
-    private void dumpProto(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
-        synchronized (mLock) {
-            final long requiredVerifierPackageToken =
-                    proto.start(PackageServiceDumpProto.REQUIRED_VERIFIER_PACKAGE);
-            proto.write(PackageServiceDumpProto.PackageShortProto.NAME, mRequiredVerifierPackage);
-            proto.write(
-                    PackageServiceDumpProto.PackageShortProto.UID,
-                    getPackageUid(
-                            mRequiredVerifierPackage,
-                            MATCH_DEBUG_TRIAGED_MISSING,
-                            UserHandle.USER_SYSTEM));
-            proto.end(requiredVerifierPackageToken);
-
-            DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
-            ComponentName verifierComponent = proxy.getComponentName();
-            if (verifierComponent != null) {
-                String verifierPackageName = verifierComponent.getPackageName();
-                final long verifierPackageToken =
-                        proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
-                proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
-                proto.write(
-                        PackageServiceDumpProto.PackageShortProto.UID,
-                        getPackageUid(
-                                verifierPackageName,
-                                MATCH_DEBUG_TRIAGED_MISSING,
-                                UserHandle.USER_SYSTEM));
-                proto.end(verifierPackageToken);
-            }
-
-            dumpSharedLibrariesProto(proto);
-            dumpFeaturesProto(proto);
-            mSettings.dumpPackagesProto(proto);
-            mSettings.dumpSharedUsersProto(proto);
-            dumpCriticalInfo(proto);
-        }
-        proto.flush();
-    }
-
-    private void dumpFeaturesProto(ProtoOutputStream proto) {
-        synchronized (mAvailableFeatures) {
-            final int count = mAvailableFeatures.size();
-            for (int i = 0; i < count; i++) {
-                mAvailableFeatures.valueAt(i).dumpDebug(proto, PackageServiceDumpProto.FEATURES);
-            }
-        }
-    }
-
-    private void dumpSharedLibrariesProto(ProtoOutputStream proto) {
-        final int count = mSharedLibraries.size();
-        for (int i = 0; i < count; i++) {
-            final String libName = mSharedLibraries.keyAt(i);
-            WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
-            if (versionedLib == null) {
-                continue;
-            }
-            final int versionCount = versionedLib.size();
-            for (int j = 0; j < versionCount; j++) {
-                final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j);
-                final long sharedLibraryToken =
-                        proto.start(PackageServiceDumpProto.SHARED_LIBRARIES);
-                proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName());
-                final boolean isJar = (libraryInfo.getPath() != null);
-                proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar);
-                if (isJar) {
-                    proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH,
-                            libraryInfo.getPath());
-                } else {
-                    proto.write(PackageServiceDumpProto.SharedLibraryProto.APK,
-                            libraryInfo.getPackageName());
-                }
-                proto.end(sharedLibraryToken);
-            }
-        }
-    }
-
     public PackageFreezer freezePackage(String packageName, String killReason) {
         return freezePackage(packageName, UserHandle.USER_ALL, killReason);
     }
@@ -12262,7 +9170,7 @@
     /**
      * Verify that given package is currently frozen.
      */
-    private void checkPackageFrozen(String packageName) {
+    void checkPackageFrozen(String packageName) {
         synchronized (mLock) {
             if (!mFrozenPackages.contains(packageName)) {
                 Slog.wtf(TAG, "Expected " + packageName + " to be frozen!", new Throwable());
@@ -12370,60 +9278,7 @@
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
             mInstantAppRegistry.onUserRemovedLPw(userId);
-            removeUnusedPackagesLPw(userManager, userId);
-        }
-    }
-
-    /**
-     * We're removing userId and would like to remove any downloaded packages
-     * that are no longer in use by any other user.
-     * @param userId the user being removed
-     */
-    @GuardedBy("mLock")
-    private void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
-        final boolean DEBUG_CLEAN_APKS = false;
-        int [] users = userManager.getUserIds();
-        final int numPackages = mSettings.getPackagesLocked().size();
-        for (int index = 0; index < numPackages; index++) {
-            final PackageSetting ps = mSettings.getPackagesLocked().valueAt(index);
-            if (ps.getPkg() == null) {
-                continue;
-            }
-            final String packageName = ps.getPkg().getPackageName();
-            // Skip over if system app or static shared library
-            if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
-                    || !TextUtils.isEmpty(ps.getPkg().getStaticSharedLibName())) {
-                continue;
-            }
-            if (DEBUG_CLEAN_APKS) {
-                Slog.i(TAG, "Checking package " + packageName);
-            }
-            boolean keep = shouldKeepUninstalledPackageLPr(packageName);
-            if (keep) {
-                if (DEBUG_CLEAN_APKS) {
-                    Slog.i(TAG, "  Keeping package " + packageName + " - requested by DO");
-                }
-            } else {
-                for (int i = 0; i < users.length; i++) {
-                    if (users[i] != userId && ps.getInstalled(users[i])) {
-                        keep = true;
-                        if (DEBUG_CLEAN_APKS) {
-                            Slog.i(TAG, "  Keeping package " + packageName + " for user "
-                                    + users[i]);
-                        }
-                        break;
-                    }
-                }
-            }
-            if (!keep) {
-                if (DEBUG_CLEAN_APKS) {
-                    Slog.i(TAG, "  Removing package " + packageName);
-                }
-                //end run
-                mHandler.post(() -> mDeletePackageHelper.deletePackageX(
-                        packageName, PackageManager.VERSION_CODE_HIGHEST,
-                        userId, 0, true /*removedBySystem*/));
-            }
+            mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
         }
     }
 
@@ -12461,7 +9316,7 @@
         }
     }
 
-    boolean readPermissionStateForUser(@UserIdInt int userId) {
+    private boolean readPermissionStateForUser(@UserIdInt int userId) {
         synchronized (mLock) {
             mPermissionManager.writeLegacyPermissionStateTEMP();
             mSettings.readPermissionStateForUserSyncLPr(userId);
@@ -12511,7 +9366,7 @@
         return mArtManagerService;
     }
 
-    private boolean userNeedsBadging(int userId) {
+    boolean userNeedsBadging(int userId) {
         int index = mUserNeedsBadging.indexOfKey(userId);
         if (index < 0) {
             final UserInfo userInfo;
@@ -13107,85 +9962,9 @@
             return disabledPkg == null ? null : disabledPkg.getPackageName();
         }
 
-        /**
-         * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
-         *
-         * @param pkgNames The packages to filter
-         *
-         * @return The filtered packages
-         */
-        private @NonNull String[] filterOnlySystemPackages(@Nullable String... pkgNames) {
-            if (pkgNames == null) {
-                return ArrayUtils.emptyArray(String.class);
-            }
-
-            ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
-
-            for (String pkgName: pkgNames) {
-                synchronized (mLock) {
-                    if (pkgName == null) {
-                        continue;
-                    }
-
-                    AndroidPackage pkg = getPackage(pkgName);
-                    if (pkg == null) {
-                        Log.w(TAG, "Could not find package " + pkgName);
-                        continue;
-                    }
-
-                    if (!pkg.isSystem()) {
-                        Log.w(TAG, pkgName + " is not system");
-                        continue;
-                    }
-
-                    systemPackageNames.add(pkgName);
-                }
-            }
-
-            return systemPackageNames.toArray(new String[]{});
-        }
-
         @Override
         public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
-            return getKnownPackageNamesInternal(knownPackage, userId);
-        }
-
-        private String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
-            switch (knownPackage) {
-                case PackageManagerInternal.PACKAGE_BROWSER:
-                    return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
-                case PackageManagerInternal.PACKAGE_INSTALLER:
-                    return filterOnlySystemPackages(mRequiredInstallerPackage);
-                case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
-                    return filterOnlySystemPackages(mSetupWizardPackage);
-                case PackageManagerInternal.PACKAGE_SYSTEM:
-                    return new String[]{"android"};
-                case PackageManagerInternal.PACKAGE_VERIFIER:
-                    return filterOnlySystemPackages(mRequiredVerifierPackage);
-                case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
-                    return filterOnlySystemPackages(
-                            mDefaultTextClassifierPackage, mSystemTextClassifierPackageName);
-                case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
-                    return filterOnlySystemPackages(mRequiredPermissionControllerPackage);
-                case PackageManagerInternal.PACKAGE_CONFIGURATOR:
-                    return filterOnlySystemPackages(mConfiguratorPackage);
-                case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
-                    return filterOnlySystemPackages(mIncidentReportApproverPackage);
-                case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
-                    return filterOnlySystemPackages(mAppPredictionServicePackage);
-                case PackageManagerInternal.PACKAGE_COMPANION:
-                    return filterOnlySystemPackages(COMPANION_PACKAGE_NAME);
-                case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
-                    return TextUtils.isEmpty(mRetailDemoPackage)
-                            ? ArrayUtils.emptyArray(String.class)
-                            : new String[] {mRetailDemoPackage};
-                case PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE:
-                    return filterOnlySystemPackages(getOverlayConfigSignaturePackageName());
-                case PackageManagerInternal.PACKAGE_RECENTS:
-                    return filterOnlySystemPackages(mRecentsPackage);
-                default:
-                    return ArrayUtils.emptyArray(String.class);
-            }
+            return PackageManagerService.this.getKnownPackageNamesInternal(knownPackage, userId);
         }
 
         @Override
@@ -13367,8 +10146,8 @@
         @Override
         public List<ResolveInfo> queryIntentReceivers(Intent intent,
                 String resolvedType, int flags, int filterCallingUid, int userId) {
-            return PackageManagerService.this.queryIntentReceiversInternal(intent, resolvedType,
-                    flags, userId, filterCallingUid);
+            return PackageManagerService.this.mResolveIntentHelper.queryIntentReceiversInternal(
+                    intent, resolvedType, flags, userId, filterCallingUid);
         }
 
         @Override
@@ -13661,7 +10440,7 @@
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
                 int flags, int privateResolveFlags, int userId, boolean resolveForStart,
                 int filterCallingUid) {
-            return resolveIntentInternal(
+            return mResolveIntentHelper.resolveIntentInternal(
                     intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
                     filterCallingUid);
         }
@@ -13669,7 +10448,8 @@
         @Override
         public ResolveInfo resolveService(Intent intent, String resolvedType,
                 int flags, int userId, int callingUid) {
-            return resolveServiceInternal(intent, resolvedType, flags, userId, callingUid);
+            return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
+                    callingUid);
         }
 
         @Override
@@ -14613,7 +11393,8 @@
     private void applyMimeGroupChanges(String packageName, String mimeGroup) {
         if (mComponentResolver.updateMimeGroup(packageName, mimeGroup)) {
             Binder.withCleanCallingIdentity(() ->
-                    clearPackagePreferredActivities(packageName, UserHandle.USER_ALL));
+                    mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
+                            UserHandle.USER_ALL));
         }
 
         mPmInternal.writeSettings(false);
@@ -14710,7 +11491,7 @@
         }
     }
 
-    private static String getDefaultTimeouts() {
+    static String getDefaultTimeouts() {
         final long token = Binder.clearCallingIdentity();
         try {
             return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -14720,7 +11501,7 @@
         }
     }
 
-    private static String getKnownDigestersList() {
+    static String getKnownDigestersList() {
         final long token = Binder.clearCallingIdentity();
         try {
             return DeviceConfig.getString(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -14847,54 +11628,15 @@
         }
     }
 
+    boolean shouldKeepUninstalledPackageLPr(String packageName) {
+        return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+    }
+
     @Override
     public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
             String featureId, int userId) throws RemoteException {
-        Objects.requireNonNull(packageName);
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
-                false /* checkShell */, "get launch intent sender for package");
-        final int packageUid = getPackageUid(callingPackage, 0 /* flags */, userId);
-        if (!UserHandle.isSameApp(callingUid, packageUid)) {
-            throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
-                    + callingUid + " does not own package: " + callingPackage);
-        }
-
-        // Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
-        // Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
-        final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
-        intentToResolve.addCategory(Intent.CATEGORY_INFO);
-        intentToResolve.setPackage(packageName);
-        String resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
-        List<ResolveInfo> ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
-                0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
-                true /* resolveForStart */, false /* allowDynamicSplits */);
-        if (ris == null || ris.size() <= 0) {
-            intentToResolve.removeCategory(Intent.CATEGORY_INFO);
-            intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
-            intentToResolve.setPackage(packageName);
-            resolvedType = intentToResolve.resolveTypeIfNeeded(mContext.getContentResolver());
-            ris = queryIntentActivitiesInternal(intentToResolve, resolvedType,
-                    0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
-                    true /* resolveForStart */, false /* allowDynamicSplits */);
-        }
-
-        final Intent intent = new Intent(intentToResolve);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        // For the case of empty result, no component name is assigned into the intent. A
-        // non-launchable IntentSender which contains the failed intent is created. The
-        // SendIntentException is thrown if the IntentSender#sendIntent is invoked.
-        if (ris != null && !ris.isEmpty()) {
-            intent.setClassName(ris.get(0).activityInfo.packageName,
-                    ris.get(0).activityInfo.name);
-        }
-        final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
-                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
-                featureId, null /* token */, null /* resultWho */,
-                1 /* requestCode */, new Intent[] { intent },
-                resolvedType != null ? new String[] { resolvedType } : null,
-                PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
-        return new IntentSender(target);
+        return mResolveIntentHelper.getLaunchIntentSenderForPackage(packageName, callingPackage,
+                featureId, userId);
     }
 
     @Override
@@ -14928,31 +11670,137 @@
         }
     }
 
-    public boolean getSafeMode() {
+    boolean getSafeMode() {
         return mSafeMode;
     }
 
-    public ComponentName getResolveComponentName() {
+    ComponentName getResolveComponentName() {
         return mResolveComponentName;
     }
 
-    public DefaultAppProvider getDefaultAppProvider() {
+    DefaultAppProvider getDefaultAppProvider() {
         return mDefaultAppProvider;
     }
 
-    public File getCacheDir() {
+    File getCacheDir() {
         return mCacheDir;
     }
 
-    public List<ScanPartition> getDirsToScanAsSystem() {
+    List<ScanPartition> getDirsToScanAsSystem() {
         return mDirsToScanAsSystem;
     }
 
-    public PackageProperty getPackageProperty() {
+    PackageProperty getPackageProperty() {
         return mPackageProperty;
     }
 
-    public WatchedArrayMap<ComponentName, ParsedInstrumentation> getInstrumentation() {
+    WatchedArrayMap<ComponentName, ParsedInstrumentation> getInstrumentation() {
         return mInstrumentation;
     }
+
+    int getSdkVersion() {
+        return mSdkVersion;
+    }
+
+    void addAllPackageProperties(@NonNull AndroidPackage pkg) {
+        mPackageProperty.addAllProperties(pkg);
+    }
+
+    void addInstrumentation(ComponentName name, ParsedInstrumentation instrumentation) {
+        mInstrumentation.put(name, instrumentation);
+    }
+
+    String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
+        switch (knownPackage) {
+            case PackageManagerInternal.PACKAGE_BROWSER:
+                return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
+            case PackageManagerInternal.PACKAGE_INSTALLER:
+                return filterOnlySystemPackages(mRequiredInstallerPackage);
+            case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+                return filterOnlySystemPackages(mSetupWizardPackage);
+            case PackageManagerInternal.PACKAGE_SYSTEM:
+                return new String[]{"android"};
+            case PackageManagerInternal.PACKAGE_VERIFIER:
+                return filterOnlySystemPackages(mRequiredVerifierPackage);
+            case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
+                return filterOnlySystemPackages(
+                        mDefaultTextClassifierPackage, mSystemTextClassifierPackageName);
+            case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
+                return filterOnlySystemPackages(mRequiredPermissionControllerPackage);
+            case PackageManagerInternal.PACKAGE_CONFIGURATOR:
+                return filterOnlySystemPackages(mConfiguratorPackage);
+            case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
+                return filterOnlySystemPackages(mIncidentReportApproverPackage);
+            case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
+                return filterOnlySystemPackages(mAppPredictionServicePackage);
+            case PackageManagerInternal.PACKAGE_COMPANION:
+                return filterOnlySystemPackages(COMPANION_PACKAGE_NAME);
+            case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
+                return TextUtils.isEmpty(mRetailDemoPackage)
+                        ? ArrayUtils.emptyArray(String.class)
+                        : new String[] {mRetailDemoPackage};
+            case PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE:
+                return filterOnlySystemPackages(getOverlayConfigSignaturePackageName());
+            case PackageManagerInternal.PACKAGE_RECENTS:
+                return filterOnlySystemPackages(mRecentsPackage);
+            default:
+                return ArrayUtils.emptyArray(String.class);
+        }
+    }
+
+    /**
+     * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
+     *
+     * @param pkgNames The packages to filter
+     *
+     * @return The filtered packages
+     */
+    private @NonNull String[] filterOnlySystemPackages(@Nullable String... pkgNames) {
+        if (pkgNames == null) {
+            return ArrayUtils.emptyArray(String.class);
+        }
+
+        ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
+
+        for (String pkgName: pkgNames) {
+            synchronized (mLock) {
+                if (pkgName == null) {
+                    continue;
+                }
+
+                AndroidPackage pkg = getPackage(pkgName);
+                if (pkg == null) {
+                    Log.w(TAG, "Could not find package " + pkgName);
+                    continue;
+                }
+
+                if (!pkg.isSystem()) {
+                    Log.w(TAG, pkgName + " is not system");
+                    continue;
+                }
+
+                systemPackageNames.add(pkgName);
+            }
+        }
+
+        return systemPackageNames.toArray(new String[]{});
+    }
+
+    String getActiveLauncherPackageName(int userId) {
+        return mDefaultAppProvider.getDefaultHome(userId);
+    }
+
+    boolean setActiveLauncherPackage(@NonNull String packageName, @UserIdInt int userId,
+            @NonNull Consumer<Boolean> callback) {
+        return mDefaultAppProvider.setDefaultHome(packageName, userId, mContext.getMainExecutor(),
+                callback);
+    }
+
+    void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
+        mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+    }
+
+    ResolveInfo getInstantAppInstallerInfo() {
+        return mInstantAppInstallerInfo;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index a63cc36..97a09ff 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -134,6 +134,7 @@
     private final Singleton<DomainVerificationManagerInternal>
             mDomainVerificationManagerInternalProducer;
     private final Singleton<Handler> mHandlerProducer;
+    private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
             Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -168,7 +169,8 @@
             Producer<Handler> handlerProducer,
             SystemWrapper systemWrapper,
             ServiceProducer getLocalServiceProducer,
-            ServiceProducer getSystemServiceProducer) {
+            ServiceProducer getSystemServiceProducer,
+            Producer<BackgroundDexOptService> backgroundDexOptService) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -217,6 +219,7 @@
                 new Singleton<>(
                         domainVerificationManagerInternalProducer);
         mHandlerProducer = new Singleton<>(handlerProducer);
+        mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
     }
 
     /**
@@ -377,6 +380,10 @@
         return getLocalService(ActivityManagerInternal.class);
     }
 
+    public BackgroundDexOptService getBackgroundDexOptService() {
+        return mBackgroundDexOptService.get(this, mPackageManager);
+    }
+
     /** Provides an abstraction to static access to system state. */
     public interface SystemWrapper {
         void disablePackageCaches();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 2870c45..c1c32dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -100,5 +100,6 @@
     public boolean isEngBuild;
     public boolean isUserDebugBuild;
     public int sdkInt = Build.VERSION.SDK_INT;
+    public BackgroundDexOptService backgroundDexOptService;
     public final String incrementalVersion = Build.VERSION.INCREMENTAL;
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e4f6398..3554cc0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -226,6 +226,8 @@
                     return runForceDexOpt();
                 case "bg-dexopt-job":
                     return runDexoptJob();
+                case "cancel-bg-dexopt-job":
+                    return cancelBgDexOptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
                 case "snapshot-profile":
@@ -1863,12 +1865,18 @@
         while ((arg = getNextArg()) != null) {
             packageNames.add(arg);
         }
-        boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
-                packageNames);
+        boolean result = BackgroundDexOptService.getService().runBackgroundDexoptJob(
+                packageNames.isEmpty() ? null : packageNames);
         getOutPrintWriter().println(result ? "Success" : "Failure");
         return result ? 0 : -1;
     }
 
+    private int cancelBgDexOptJob() throws RemoteException {
+        BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
+        getOutPrintWriter().println("Success");
+        return 0;
+    }
+
     private int runDumpProfiles() throws RemoteException {
         String packageName = getNextArg();
         mInterface.dumpProfiles(packageName);
@@ -3940,6 +3948,11 @@
         pw.println("    overlap with the actual job but the job scheduler will not be able to");
         pw.println("    cancel it. It will also run even if the device is not in the idle");
         pw.println("    maintenance mode.");
+        pw.println("  cancel-bg-dexopt-job");
+        pw.println("    Cancels currently running background optimizations immediately.");
+        pw.println("    This cancels optimizations run from bg-dexopt-job or from JobScjeduler.");
+        pw.println("    Note that cancelling currently running bg-dexopt-job command requires");
+        pw.println("    running this command from separate adb shell.");
         pw.println("");
         pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
         pw.println("    Reconciles the package secondary dex files with the generated oat files.");
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
new file mode 100644
index 0000000..8c05ba7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -0,0 +1,736 @@
+/*
+ * 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.pm;
+
+import static android.content.Intent.CATEGORY_DEFAULT;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP;
+import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.PrintStreamPrinter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+final class PreferredActivityHelper {
+    // XML tags for backup/restore of various bits of state
+    private static final String TAG_PREFERRED_BACKUP = "pa";
+    private static final String TAG_DEFAULT_APPS = "da";
+
+    private final PackageManagerService mPm;
+
+    // TODO(b/198166813): remove PMS dependency
+    PreferredActivityHelper(PackageManagerService pm) {
+        mPm = pm;
+    }
+
+    private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType,
+            int flags, List<ResolveInfo> query, boolean always, boolean removeMatches,
+            boolean debug, int userId) {
+        return findPreferredActivityNotLocked(
+                intent, resolvedType, flags, query, always, removeMatches, debug, userId,
+                UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
+    }
+
+    // TODO: handle preferred activities missing while user has amnesia
+    /** <b>must not hold {@link PackageManagerService.mLock}</b> */
+    public ResolveInfo findPreferredActivityNotLocked(
+            Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+            boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+        if (Thread.holdsLock(mPm.mLock)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+        }
+        if (!mPm.mUserManager.exists(userId)) return null;
+
+        PackageManagerService.FindPreferredActivityBodyResult body =
+                mPm.findPreferredActivityInternal(
+                intent, resolvedType, flags, query, always,
+                removeMatches, debug, userId, queryMayBeFiltered);
+        if (body.mChanged) {
+            if (DEBUG_PREFERRED) {
+                Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
+            }
+            synchronized (mPm.mLock) {
+                mPm.scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+        if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+            Slog.v(TAG, "No preferred activity to return");
+        }
+        return body.mPreferredResolveInfo;
+    }
+
+    /** This method takes a specific user id as well as UserHandle.USER_ALL. */
+    public void clearPackagePreferredActivities(String packageName, int userId) {
+        final SparseBooleanArray changedUsers = new SparseBooleanArray();
+        synchronized (mPm.mLock) {
+            mPm.clearPackagePreferredActivitiesLPw(packageName, changedUsers, userId);
+        }
+        if (changedUsers.size() > 0) {
+            updateDefaultHomeNotLocked(changedUsers);
+            mPm.postPreferredActivityChangedBroadcast(userId);
+            synchronized (mPm.mLock) {
+                mPm.scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+    }
+
+    /**
+     * <b>must not hold {@link PackageManagerService.mLock}</b>
+     *
+     * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
+     */
+    public boolean updateDefaultHomeNotLocked(int userId) {
+        if (Thread.holdsLock(mPm.mLock)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+        }
+        if (!mPm.mSystemReady) {
+            // We might get called before system is ready because of package changes etc, but
+            // finding preferred activity depends on settings provider, so we ignore the update
+            // before that.
+            return false;
+        }
+        final Intent intent = mPm.getHomeIntent();
+        final List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesInternal(intent, null,
+                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+        final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
+                intent, null, 0, resolveInfos, true, false, false, userId);
+        final String packageName = preferredResolveInfo != null
+                && preferredResolveInfo.activityInfo != null
+                ? preferredResolveInfo.activityInfo.packageName : null;
+        final String currentPackageName = mPm.getActiveLauncherPackageName(userId);
+        if (TextUtils.equals(currentPackageName, packageName)) {
+            return false;
+        }
+        final String[] callingPackages = mPm.getPackagesForUid(Binder.getCallingUid());
+        if (callingPackages != null && ArrayUtils.contains(callingPackages,
+                mPm.mRequiredPermissionControllerPackage)) {
+            // PermissionController manages default home directly.
+            return false;
+        }
+
+        if (packageName == null) {
+            // Keep the default home package in RoleManager.
+            return false;
+        }
+        return mPm.setActiveLauncherPackage(packageName, userId,
+                successful -> {
+                    if (successful) {
+                        mPm.postPreferredActivityChangedBroadcast(userId);
+                    }
+                });
+    }
+
+    /**
+     * Variant that takes a {@link WatchedIntentFilter}
+     */
+    public void addPreferredActivity(WatchedIntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity, boolean always, int userId,
+            String opname, boolean removeExisting) {
+        // writer
+        int callingUid = Binder.getCallingUid();
+        mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+                false /* checkShell */, "add preferred activity");
+        if (mPm.mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            synchronized (mPm.mLock) {
+                if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
+                            + callingUid);
+                    return;
+                }
+            }
+            mPm.mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+        }
+        if (filter.countActions() == 0) {
+            Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+            return;
+        }
+        if (DEBUG_PREFERRED) {
+            Slog.i(TAG, opname + " activity " + activity.flattenToShortString() + " for user "
+                    + userId + ":");
+            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+        }
+        synchronized (mPm.mLock) {
+            final PreferredIntentResolver pir = mPm.mSettings.editPreferredActivitiesLPw(userId);
+            final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
+            if (removeExisting && existing != null) {
+                Settings.removeFilters(pir, filter, existing);
+            }
+            pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
+            mPm.scheduleWritePackageRestrictionsLocked(userId);
+        }
+        if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
+            mPm.postPreferredActivityChangedBroadcast(userId);
+        }
+    }
+
+    /**
+     * Variant that takes a {@link WatchedIntentFilter}
+     */
+    public void replacePreferredActivity(WatchedIntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity, int userId) {
+        if (filter.countActions() != 1) {
+            throw new IllegalArgumentException(
+                    "replacePreferredActivity expects filter to have only 1 action.");
+        }
+        if (filter.countDataAuthorities() != 0
+                || filter.countDataPaths() != 0
+                || filter.countDataSchemes() > 1
+                || filter.countDataTypes() != 0) {
+            throw new IllegalArgumentException(
+                    "replacePreferredActivity expects filter to have no data authorities, "
+                            + "paths, or types; and at most one scheme.");
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        mPm.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
+                false /* checkShell */, "replace preferred activity");
+        if (mPm.mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            synchronized (mPm.mLock) {
+                if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
+                            + Binder.getCallingUid());
+                    return;
+                }
+            }
+            mPm.mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+        }
+
+        synchronized (mPm.mLock) {
+            final PreferredIntentResolver pir = mPm.mSettings.getPreferredActivities(userId);
+            if (pir != null) {
+                // Get all of the existing entries that exactly match this filter.
+                final ArrayList<PreferredActivity> existing = pir.findFilters(filter);
+                if (existing != null && existing.size() == 1) {
+                    final PreferredActivity cur = existing.get(0);
+                    if (DEBUG_PREFERRED) {
+                        Slog.i(TAG, "Checking replace of preferred:");
+                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+                        if (!cur.mPref.mAlways) {
+                            Slog.i(TAG, "  -- CUR; not mAlways!");
+                        } else {
+                            Slog.i(TAG, "  -- CUR: mMatch=" + cur.mPref.mMatch);
+                            Slog.i(TAG, "  -- CUR: mSet="
+                                    + Arrays.toString(cur.mPref.mSetComponents));
+                            Slog.i(TAG, "  -- CUR: mComponent=" + cur.mPref.mShortComponent);
+                            Slog.i(TAG, "  -- NEW: mMatch="
+                                    + (match & IntentFilter.MATCH_CATEGORY_MASK));
+                            Slog.i(TAG, "  -- CUR: mSet=" + Arrays.toString(set));
+                            Slog.i(TAG, "  -- CUR: mComponent=" + activity.flattenToShortString());
+                        }
+                    }
+                    if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity)
+                            && cur.mPref.mMatch == (match & IntentFilter.MATCH_CATEGORY_MASK)
+                            && cur.mPref.sameSet(set)) {
+                        // Setting the preferred activity to what it happens to be already
+                        if (DEBUG_PREFERRED) {
+                            Slog.i(TAG, "Replacing with same preferred activity "
+                                    + cur.mPref.mShortComponent + " for user "
+                                    + userId + ":");
+                            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+                        }
+                        return;
+                    }
+                }
+                if (existing != null) {
+                    Settings.removeFilters(pir, filter, existing);
+                }
+            }
+        }
+        addPreferredActivity(filter, match, set, activity, true, userId,
+                "Replacing preferred", false);
+    }
+
+    public void clearPackagePreferredActivities(String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPm.getInstantAppPackageName(callingUid) != null) {
+            return;
+        }
+        // writer
+        synchronized (mPm.mLock) {
+            AndroidPackage pkg = mPm.mPackages.get(packageName);
+            if (pkg == null || !mPm.isCallerSameApp(packageName, callingUid)) {
+                if (mPm.mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    if (mPm.getUidTargetSdkVersionLockedLPr(callingUid)
+                            < Build.VERSION_CODES.FROYO) {
+                        Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid "
+                                + callingUid);
+                        return;
+                    }
+                    mPm.mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+                }
+            }
+            final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+            if (ps != null
+                    && mPm.shouldFilterApplicationLocked(
+                    ps, callingUid, UserHandle.getUserId(callingUid))) {
+                return;
+            }
+        }
+        int callingUserId = UserHandle.getCallingUserId();
+        clearPackagePreferredActivities(packageName, callingUserId);
+    }
+
+    /** <b>must not hold {@link #PackageManagerService.mLock}</b> */
+    void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
+        if (Thread.holdsLock(mPm.mLock)) {
+            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+                    + " is holding mLock", new Throwable());
+        }
+        for (int i = userIds.size() - 1; i >= 0; --i) {
+            final int userId = userIds.keyAt(i);
+            updateDefaultHomeNotLocked(userId);
+        }
+    }
+
+    public void setHomeActivity(ComponentName comp, int userId) {
+        if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            return;
+        }
+        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+        mPm.getHomeActivitiesAsUser(homeActivities, userId);
+
+        boolean found = false;
+
+        final int size = homeActivities.size();
+        final ComponentName[] set = new ComponentName[size];
+        for (int i = 0; i < size; i++) {
+            final ResolveInfo candidate = homeActivities.get(i);
+            final ActivityInfo info = candidate.activityInfo;
+            final ComponentName activityName = new ComponentName(info.packageName, info.name);
+            set[i] = activityName;
+            if (!found && activityName.equals(comp)) {
+                found = true;
+            }
+        }
+        if (!found) {
+            throw new IllegalArgumentException("Component " + comp + " cannot be home on user "
+                    + userId);
+        }
+        replacePreferredActivity(getHomeFilter(), IntentFilter.MATCH_CATEGORY_EMPTY,
+                set, comp, userId);
+    }
+
+    private WatchedIntentFilter getHomeFilter() {
+        WatchedIntentFilter filter = new WatchedIntentFilter(Intent.ACTION_MAIN);
+        filter.addCategory(Intent.CATEGORY_HOME);
+        filter.addCategory(Intent.CATEGORY_DEFAULT);
+        return filter;
+    }
+
+    /**
+     * Variant that takes a {@link WatchedIntentFilter}
+     */
+    public void addPersistentPreferredActivity(WatchedIntentFilter filter, ComponentName activity,
+            int userId) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException(
+                    "addPersistentPreferredActivity can only be run by the system");
+        }
+        if (filter.countActions() == 0) {
+            Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+            return;
+        }
+        if (DEBUG_PREFERRED) {
+            Slog.i(TAG, "Adding persistent preferred activity " + activity
+                    + " for user " + userId + ":");
+            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+        }
+        synchronized (mPm.mLock) {
+            mPm.mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
+                    new PersistentPreferredActivity(filter, activity, true));
+            mPm.scheduleWritePackageRestrictionsLocked(userId);
+        }
+        if (isHomeFilter(filter)) {
+            updateDefaultHomeNotLocked(userId);
+        }
+        mPm.postPreferredActivityChangedBroadcast(userId);
+    }
+
+    public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException(
+                    "clearPackagePersistentPreferredActivities can only be run by the system");
+        }
+        boolean changed = false;
+        synchronized (mPm.mLock) {
+            changed = mPm.mSettings.clearPackagePersistentPreferredActivities(packageName, userId);
+        }
+        if (changed) {
+            updateDefaultHomeNotLocked(userId);
+            mPm.postPreferredActivityChangedBroadcast(userId);
+            synchronized (mPm.mLock) {
+                mPm.scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+    }
+
+    private boolean isHomeFilter(@NonNull WatchedIntentFilter filter) {
+        return filter.hasAction(Intent.ACTION_MAIN) && filter.hasCategory(Intent.CATEGORY_HOME)
+                && filter.hasCategory(CATEGORY_DEFAULT);
+    }
+
+    /**
+     * Common machinery for picking apart a restored XML blob and passing
+     * it to a caller-supplied functor to be applied to the running system.
+     */
+    private void restoreFromXml(TypedXmlPullParser parser, int userId,
+            String expectedStartTag, BlobXmlRestorer functor)
+            throws IOException, XmlPullParserException {
+        int type;
+        while ((type = parser.next()) != XmlPullParser.START_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
+        }
+        if (type != XmlPullParser.START_TAG) {
+            // oops didn't find a start tag?!
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Didn't find start tag during restore");
+            }
+            return;
+        }
+        // this is supposed to be TAG_PREFERRED_BACKUP
+        if (!expectedStartTag.equals(parser.getName())) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Found unexpected tag " + parser.getName());
+            }
+            return;
+        }
+
+        // skip interfering stuff, then we're aligned with the backing implementation
+        while ((type = parser.next()) == XmlPullParser.TEXT) { }
+        functor.apply(parser, userId);
+    }
+
+    private interface BlobXmlRestorer {
+        void apply(TypedXmlPullParser parser, int userId)
+                throws IOException, XmlPullParserException;
+    }
+
+    public byte[] getPreferredActivityBackup(int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call getPreferredActivityBackup()");
+        }
+
+        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+        try {
+            final TypedXmlSerializer serializer = Xml.newFastSerializer();
+            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null, true);
+            serializer.startTag(null, TAG_PREFERRED_BACKUP);
+
+            synchronized (mPm.mLock) {
+                mPm.mSettings.writePreferredActivitiesLPr(serializer, userId, true);
+            }
+
+            serializer.endTag(null, TAG_PREFERRED_BACKUP);
+            serializer.endDocument();
+            serializer.flush();
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Unable to write preferred activities for backup", e);
+            }
+            return null;
+        }
+
+        return dataStream.toByteArray();
+    }
+
+    public void restorePreferredActivities(byte[] backup, int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call restorePreferredActivities()");
+        }
+
+        try {
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+            restoreFromXml(parser, userId, TAG_PREFERRED_BACKUP,
+                    (readParser, readUserId) -> {
+                        synchronized (mPm.mLock) {
+                            mPm.mSettings.readPreferredActivitiesLPw(readParser, readUserId);
+                        }
+                        updateDefaultHomeNotLocked(readUserId);
+                    });
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Non-Binder method, support for the backup/restore mechanism: write the
+     * default browser (etc) settings in its canonical XML format.  Returns the default
+     * browser XML representation as a byte array, or null if there is none.
+     */
+    public byte[] getDefaultAppsBackup(int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call getDefaultAppsBackup()");
+        }
+
+        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+        try {
+            final TypedXmlSerializer serializer = Xml.newFastSerializer();
+            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+            serializer.startDocument(null, true);
+            serializer.startTag(null, TAG_DEFAULT_APPS);
+
+            synchronized (mPm.mLock) {
+                mPm.mSettings.writeDefaultAppsLPr(serializer, userId);
+            }
+
+            serializer.endTag(null, TAG_DEFAULT_APPS);
+            serializer.endDocument();
+            serializer.flush();
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Unable to write default apps for backup", e);
+            }
+            return null;
+        }
+
+        return dataStream.toByteArray();
+    }
+
+    public void restoreDefaultApps(byte[] backup, int userId) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only the system may call restoreDefaultApps()");
+        }
+
+        try {
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+            restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
+                    (parser1, userId1) -> {
+                        final String defaultBrowser;
+                        synchronized (mPm.mLock) {
+                            mPm.mSettings.readDefaultAppsLPw(parser1, userId1);
+                            defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw(
+                                    userId1);
+                        }
+                        if (defaultBrowser != null) {
+                            mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+                        }
+                    });
+        } catch (Exception e) {
+            if (DEBUG_BACKUP) {
+                Slog.e(TAG, "Exception restoring default apps: " + e.getMessage());
+            }
+        }
+    }
+
+    public void resetApplicationPreferences(int userId) {
+        mPm.mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+        final long identity = Binder.clearCallingIdentity();
+        // writer
+        try {
+            final SparseBooleanArray changedUsers = new SparseBooleanArray();
+            synchronized (mPm.mLock) {
+                mPm.clearPackagePreferredActivitiesLPw(null, changedUsers, userId);
+            }
+            if (changedUsers.size() > 0) {
+                mPm.postPreferredActivityChangedBroadcast(userId);
+            }
+            synchronized (mPm.mLock) {
+                mPm.mSettings.applyDefaultPreferredAppsLPw(userId);
+                mPm.mDomainVerificationManager.clearUser(userId);
+                final int numPackages = mPm.mPackages.size();
+                for (int i = 0; i < numPackages; i++) {
+                    final AndroidPackage pkg = mPm.mPackages.valueAt(i);
+                    mPm.mPermissionManager.resetRuntimePermissions(pkg, userId);
+                }
+            }
+            updateDefaultHomeNotLocked(userId);
+            resetNetworkPolicies(userId);
+            synchronized (mPm.mLock) {
+                mPm.scheduleWritePackageRestrictionsLocked(userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void resetNetworkPolicies(int userId) {
+        mPm.mInjector.getLocalService(NetworkPolicyManagerInternal.class).resetUserState(userId);
+    }
+
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+            List<ComponentName> outActivities, String packageName) {
+        List<WatchedIntentFilter> temp =
+                WatchedIntentFilter.toWatchedIntentFilterList(outFilters);
+        final int result = getPreferredActivitiesInternal(
+                temp, outActivities, packageName);
+        outFilters.clear();
+        for (int i = 0; i < temp.size(); i++) {
+            outFilters.add(temp.get(i).getIntentFilter());
+        }
+        return result;
+    }
+
+    /**
+     * Variant that takes a {@link WatchedIntentFilter}
+     */
+    private int getPreferredActivitiesInternal(List<WatchedIntentFilter> outFilters,
+            List<ComponentName> outActivities, String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPm.getInstantAppPackageName(callingUid) != null) {
+            return 0;
+        }
+        int num = 0;
+        final int userId = UserHandle.getCallingUserId();
+        // reader
+        synchronized (mPm.mLock) {
+            PreferredIntentResolver pir = mPm.mSettings.getPreferredActivities(userId);
+            if (pir != null) {
+                final Iterator<PreferredActivity> it = pir.filterIterator();
+                while (it.hasNext()) {
+                    final PreferredActivity pa = it.next();
+                    final String prefPackageName = pa.mPref.mComponent.getPackageName();
+                    if (packageName == null
+                            || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) {
+                        if (mPm.shouldFilterApplicationLocked(
+                                mPm.mSettings.getPackageLPr(prefPackageName), callingUid, userId)) {
+                            continue;
+                        }
+                        if (outFilters != null) {
+                            outFilters.add(new WatchedIntentFilter(pa.getIntentFilter()));
+                        }
+                        if (outActivities != null) {
+                            outActivities.add(pa.mPref.mComponent);
+                        }
+                    }
+                }
+            }
+        }
+
+        return num;
+    }
+
+    public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) {
+        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.SYSTEM_UID)) {
+            throw new SecurityException(
+                    "findPersistentPreferredActivity can only be run by the system");
+        }
+        if (!mPm.mUserManager.exists(userId)) {
+            return null;
+        }
+        final int callingUid = Binder.getCallingUid();
+        intent = PackageManagerServiceUtils.updateIntentForResolve(intent);
+        final String resolvedType = intent.resolveTypeIfNeeded(mPm.mContext.getContentResolver());
+        final int flags = mPm.updateFlagsForResolve(
+                0, userId, callingUid, false /*includeInstantApps*/,
+                mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+                        0));
+        final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+                flags, userId);
+        synchronized (mPm.mLock) {
+            return mPm.findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
+                    userId);
+        }
+    }
+
+    /**
+     * Variant that takes a {@link WatchedIntentFilter}
+     */
+    public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+            WatchedIntentFilter filter, int match, ComponentName activity) {
+        if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            return;
+        }
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG_PREFERRED) {
+            Log.v(TAG, "setLastChosenActivity intent=" + intent
+                    + " resolvedType=" + resolvedType
+                    + " flags=" + flags
+                    + " filter=" + filter
+                    + " match=" + match
+                    + " activity=" + activity);
+            filter.dump(new PrintStreamPrinter(System.out), "    ");
+        }
+        intent.setComponent(null);
+        final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+                flags, userId);
+        // Find any earlier preferred or last chosen entries and nuke them
+        findPreferredActivityNotLocked(
+                intent, resolvedType, flags, query, false, true, false, userId);
+        // Add the new activity as the last chosen for this filter
+        addPreferredActivity(filter, match, null, activity, false, userId,
+                "Setting last chosen", false);
+    }
+
+    public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
+        if (mPm.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            return null;
+        }
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
+        final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+                flags, userId);
+        return findPreferredActivityNotLocked(
+                intent, resolvedType, flags, query, false, false, false, userId);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index bcf2f92..bb3ffb4 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -29,7 +29,10 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.VersionedPackage;
 import android.content.pm.parsing.component.ParsedInstrumentation;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
 import android.util.Log;
@@ -42,6 +45,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.utils.WatchedLongSparseArray;
 
 import java.io.File;
 import java.util.Collections;
@@ -168,7 +172,7 @@
             final int libraryNamesSize = pkg.getLibraryNames().size();
             for (i = 0; i < libraryNamesSize; i++) {
                 String name = pkg.getLibraryNames().get(i);
-                if (mPm.removeSharedLibraryLPw(name, 0)) {
+                if (removeSharedLibraryLPw(name, 0)) {
                     if (DEBUG_REMOVE && chatty) {
                         if (r == null) {
                             r = new StringBuilder(256);
@@ -185,7 +189,7 @@
 
         // Any package can hold static shared libraries.
         if (pkg.getStaticSharedLibName() != null) {
-            if (mPm.removeSharedLibraryLPw(pkg.getStaticSharedLibName(),
+            if (removeSharedLibraryLPw(pkg.getStaticSharedLibName(),
                     pkg.getStaticSharedLibVersion())) {
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
@@ -203,6 +207,44 @@
         }
     }
 
+    private boolean removeSharedLibraryLPw(String name, long version) {
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(name);
+        if (versionedLib == null) {
+            return false;
+        }
+        final int libIdx = versionedLib.indexOfKey(version);
+        if (libIdx < 0) {
+            return false;
+        }
+        SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+        // Remove the shared library overlays from its dependent packages.
+        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+            final List<VersionedPackage> dependents = mPm.getPackagesUsingSharedLibraryLPr(
+                    libraryInfo, 0, Process.SYSTEM_UID, currentUserId);
+            if (dependents == null) {
+                continue;
+            }
+            for (VersionedPackage dependentPackage : dependents) {
+                final PackageSetting ps = mPm.mSettings.getPackageLPr(
+                        dependentPackage.getPackageName());
+                if (ps != null) {
+                    ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+                }
+            }
+        }
+
+        versionedLib.remove(version);
+        if (versionedLib.size() <= 0) {
+            mPm.mSharedLibraries.remove(name);
+            if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) {
+                mPm.mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage()
+                        .getPackageName());
+            }
+        }
+        return true;
+    }
+
     /*
      * This method deletes the package from internal data structures. If the DELETE_KEEP_DATA
      * flag is not set, the data directory is removed as well.
@@ -274,7 +316,9 @@
                 mPm.mSettings.removeRenamedPackageLPw(deletedPs.getRealName());
             }
             if (changedUsers.size() > 0) {
-                mPm.updateDefaultHomeNotLocked(changedUsers);
+                final PreferredActivityHelper preferredActivityHelper =
+                        new PreferredActivityHelper(mPm);
+                preferredActivityHelper.updateDefaultHomeNotLocked(changedUsers);
                 mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
             }
         }
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
new file mode 100644
index 0000000..cead177
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -0,0 +1,709 @@
+/*
+ * 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.pm;
+
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
+import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+final class ResolveIntentHelper {
+    private final PackageManagerService mPm;
+    private final PreferredActivityHelper mPreferredActivityHelper;
+
+    // TODO(b/198166813): remove PMS dependency
+    ResolveIntentHelper(PackageManagerService pm, PreferredActivityHelper preferredActivityHelper) {
+        mPm = pm;
+        mPreferredActivityHelper = preferredActivityHelper;
+    }
+
+    /**
+     * Normally instant apps can only be resolved when they're visible to the caller.
+     * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+     * since we need to allow the system to start any installed application.
+     */
+    public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
+            @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, int userId,
+            boolean resolveForStart, int filterCallingUid) {
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
+
+            if (!mPm.mUserManager.exists(userId)) return null;
+            final int callingUid = Binder.getCallingUid();
+            flags = mPm.updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
+                    mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+                            resolvedType, flags));
+            mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+                    false /*checkShell*/, "resolve intent");
+
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
+            final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+                    flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
+                    true /*allowDynamicSplits*/);
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+            final boolean queryMayBeFiltered =
+                    UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
+                            && !resolveForStart;
+
+            final ResolveInfo bestChoice =
+                    chooseBestActivity(
+                            intent, resolvedType, flags, privateResolveFlags, query, userId,
+                            queryMayBeFiltered);
+            final boolean nonBrowserOnly =
+                    (privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
+            if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
+                return null;
+            }
+            return bestChoice;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
+            int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
+            boolean queryMayBeFiltered) {
+        if (query != null) {
+            final int n = query.size();
+            if (n == 1) {
+                return query.get(0);
+            } else if (n > 1) {
+                final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+                // If there is more than one activity with the same priority,
+                // then let the user decide between them.
+                ResolveInfo r0 = query.get(0);
+                ResolveInfo r1 = query.get(1);
+                if (DEBUG_INTENT_MATCHING || debug) {
+                    Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+                            + r1.activityInfo.name + "=" + r1.priority);
+                }
+                // If the first activity has a higher priority, or a different
+                // default, then it is always desirable to pick it.
+                if (r0.priority != r1.priority
+                        || r0.preferredOrder != r1.preferredOrder
+                        || r0.isDefault != r1.isDefault) {
+                    return query.get(0);
+                }
+                // If we have saved a preference for a preferred activity for
+                // this Intent, use that.
+                ResolveInfo ri = mPreferredActivityHelper.findPreferredActivityNotLocked(intent,
+                        resolvedType, flags, query, true, false, debug, userId,
+                        queryMayBeFiltered);
+                if (ri != null) {
+                    return ri;
+                }
+                int browserCount = 0;
+                for (int i = 0; i < n; i++) {
+                    ri = query.get(i);
+                    if (ri.handleAllWebDataURI) {
+                        browserCount++;
+                    }
+                    // If we have an ephemeral app, use it
+                    if (ri.activityInfo.applicationInfo.isInstantApp()) {
+                        final String packageName = ri.activityInfo.packageName;
+                        final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+                        if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
+                                mPm.mDomainVerificationManager, ps, intent, flags, userId)) {
+                            return ri;
+                        }
+                    }
+                }
+                if ((privateResolveFlags
+                        & PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
+                    return null;
+                }
+                ri = new ResolveInfo(mPm.mResolveInfo);
+                // if all resolve options are browsers, mark the resolver's info as if it were
+                // also a browser.
+                ri.handleAllWebDataURI = browserCount == n;
+                ri.activityInfo = new ActivityInfo(ri.activityInfo);
+                ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
+                // If all of the options come from the same package, show the application's
+                // label and icon instead of the generic resolver's.
+                // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
+                // and then throw away the ResolveInfo itself, meaning that the caller loses
+                // the resolvePackageName. Therefore the activityInfo.labelRes above provides
+                // a fallback for this case; we only set the target package's resources on
+                // the ResolveInfo, not the ActivityInfo.
+                final String intentPackage = intent.getPackage();
+                if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
+                    final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
+                    ri.resolvePackageName = intentPackage;
+                    if (mPm.userNeedsBadging(userId)) {
+                        ri.noResourceId = true;
+                    } else {
+                        ri.icon = appi.icon;
+                    }
+                    ri.iconResourceId = appi.icon;
+                    ri.labelRes = appi.labelRes;
+                }
+                ri.activityInfo.applicationInfo = new ApplicationInfo(
+                        ri.activityInfo.applicationInfo);
+                if (userId != 0) {
+                    ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+                            UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+                }
+                // Make sure that the resolver is displayable in car mode
+                if (ri.activityInfo.metaData == null) ri.activityInfo.metaData = new Bundle();
+                ri.activityInfo.metaData.putBoolean(Intent.METADATA_DOCK_HOME, true);
+                return ri;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return true if the given list is not empty and all of its contents have
+     * an activityInfo with the given package name.
+     */
+    private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
+        if (ArrayUtils.isEmpty(list)) {
+            return false;
+        }
+        for (int i = 0, n = list.size(); i < n; i++) {
+            final ResolveInfo ri = list.get(i);
+            final ActivityInfo ai = ri != null ? ri.activityInfo : null;
+            if (ai == null || !packageName.equals(ai.packageName)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
+            String featureId, int userId) throws RemoteException {
+        Objects.requireNonNull(packageName);
+        final int callingUid = Binder.getCallingUid();
+        mPm.enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+                false /* checkShell */, "get launch intent sender for package");
+        final int packageUid = mPm.getPackageUid(callingPackage, 0 /* flags */, userId);
+        if (!UserHandle.isSameApp(callingUid, packageUid)) {
+            throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
+                    + callingUid + " does not own package: " + callingPackage);
+        }
+
+        // Using the same implementation with the #getLaunchIntentForPackage to get the ResolveInfo.
+        // Pass the resolveForStart as true in queryIntentActivities to skip the app filtering.
+        final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+        intentToResolve.addCategory(Intent.CATEGORY_INFO);
+        intentToResolve.setPackage(packageName);
+        String resolvedType = intentToResolve.resolveTypeIfNeeded(
+                mPm.mContext.getContentResolver());
+        List<ResolveInfo> ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+                0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+                true /* resolveForStart */, false /* allowDynamicSplits */);
+        if (ris == null || ris.size() <= 0) {
+            intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+            intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+            intentToResolve.setPackage(packageName);
+            resolvedType = intentToResolve.resolveTypeIfNeeded(mPm.mContext.getContentResolver());
+            ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+                    0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
+                    true /* resolveForStart */, false /* allowDynamicSplits */);
+        }
+
+        final Intent intent = new Intent(intentToResolve);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // For the case of empty result, no component name is assigned into the intent. A
+        // non-launchable IntentSender which contains the failed intent is created. The
+        // SendIntentException is thrown if the IntentSender#sendIntent is invoked.
+        if (ris != null && !ris.isEmpty()) {
+            intent.setClassName(ris.get(0).activityInfo.packageName,
+                    ris.get(0).activityInfo.name);
+        }
+        final IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature(
+                ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+                featureId, null /* token */, null /* resultWho */,
+                1 /* requestCode */, new Intent[]{intent},
+                resolvedType != null ? new String[]{resolvedType} : null,
+                PendingIntent.FLAG_IMMUTABLE, null /* bOptions */, userId);
+        return new IntentSender(target);
+    }
+
+    // In this method, we have to know the actual calling UID, but in some cases Binder's
+    // call identity is removed, so the UID has to be passed in explicitly.
+    public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
+            String resolvedType, int flags, int userId, int filterCallingUid) {
+        if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+        mPm.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
+                false /*checkShell*/, "query intent receivers");
+        final String instantAppPkgName = mPm.getInstantAppPackageName(filterCallingUid);
+        flags = mPm.updateFlagsForResolve(
+                flags, userId, filterCallingUid, false /*includeInstantApps*/,
+                mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+                        flags));
+        Intent originalIntent = null;
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                originalIntent = intent;
+                intent = intent.getSelector();
+                comp = intent.getComponent();
+            }
+        }
+        List<ResolveInfo> list = Collections.emptyList();
+        if (comp != null) {
+            final ActivityInfo ai = mPm.getReceiverInfo(comp, flags, userId);
+            if (ai != null) {
+                // When specifying an explicit component, we prevent the activity from being
+                // used when either 1) the calling package is normal and the activity is within
+                // an instant application or 2) the calling package is ephemeral and the
+                // activity is not visible to instant applications.
+                final boolean matchInstantApp =
+                        (flags & PackageManager.MATCH_INSTANT) != 0;
+                final boolean matchVisibleToInstantAppOnly =
+                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+                final boolean matchExplicitlyVisibleOnly =
+                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
+                final boolean isCallerInstantApp =
+                        instantAppPkgName != null;
+                final boolean isTargetSameInstantApp =
+                        comp.getPackageName().equals(instantAppPkgName);
+                final boolean isTargetInstantApp =
+                        (ai.applicationInfo.privateFlags
+                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+                final boolean isTargetVisibleToInstantApp =
+                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
+                final boolean isTargetExplicitlyVisibleToInstantApp = isTargetVisibleToInstantApp
+                        && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
+                final boolean isTargetHiddenFromInstantApp = !isTargetVisibleToInstantApp
+                        || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
+                final boolean blockResolution =
+                        !isTargetSameInstantApp
+                                && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
+                                && isTargetHiddenFromInstantApp));
+                if (!blockResolution) {
+                    ResolveInfo ri = new ResolveInfo();
+                    ri.activityInfo = ai;
+                    list = new ArrayList<>(1);
+                    list.add(ri);
+                    PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
+                            mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
+                            list, true, intent, resolvedType, filterCallingUid);
+                }
+            }
+        } else {
+            // reader
+            synchronized (mPm.mLock) {
+                String pkgName = intent.getPackage();
+                if (pkgName == null) {
+                    final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
+                            intent, resolvedType, flags, userId);
+                    if (result != null) {
+                        list = result;
+                    }
+                }
+                final AndroidPackage pkg = mPm.mPackages.get(pkgName);
+                if (pkg != null) {
+                    final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
+                            intent, resolvedType, flags, pkg.getReceivers(), userId);
+                    if (result != null) {
+                        list = result;
+                    }
+                }
+            }
+        }
+
+        if (originalIntent != null) {
+            // We also have to ensure all components match the original intent
+            PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
+                    mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
+                    list, true, originalIntent, resolvedType, filterCallingUid);
+        }
+
+        return mPm.applyPostResolutionFilter(
+                list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
+    }
+
+
+    public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
+            int userId, int callingUid) {
+        if (!mPm.mUserManager.exists(userId)) return null;
+        flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+        List<ResolveInfo> query = mPm.queryIntentServicesInternal(
+                intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
+        if (query != null) {
+            if (query.size() >= 1) {
+                // If there is more than one service with the same priority,
+                // just arbitrarily pick the first one.
+                return query.get(0);
+            }
+        }
+        return null;
+    }
+
+    public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
+            Intent intent, String resolvedType, int flags, int userId) {
+        if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+        final int callingUid = Binder.getCallingUid();
+        final String instantAppPkgName = mPm.getInstantAppPackageName(callingUid);
+        flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector();
+                comp = intent.getComponent();
+            }
+        }
+        if (comp != null) {
+            final List<ResolveInfo> list = new ArrayList<>(1);
+            final ProviderInfo pi = mPm.getProviderInfo(comp, flags, userId);
+            if (pi != null) {
+                // When specifying an explicit component, we prevent the provider from being
+                // used when either 1) the provider is in an instant application and the
+                // caller is not the same instant application or 2) the calling package is an
+                // instant application and the provider is not visible to instant applications.
+                final boolean matchInstantApp =
+                        (flags & PackageManager.MATCH_INSTANT) != 0;
+                final boolean matchVisibleToInstantAppOnly =
+                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+                final boolean isCallerInstantApp =
+                        instantAppPkgName != null;
+                final boolean isTargetSameInstantApp =
+                        comp.getPackageName().equals(instantAppPkgName);
+                final boolean isTargetInstantApp =
+                        (pi.applicationInfo.privateFlags
+                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+                final boolean isTargetHiddenFromInstantApp =
+                        (pi.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
+                final boolean blockResolution =
+                        !isTargetSameInstantApp
+                                && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
+                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
+                                && isTargetHiddenFromInstantApp));
+                final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
+                        && mPm.shouldFilterApplicationLocked(
+                        mPm.getPackageSettingInternal(pi.applicationInfo.packageName,
+                                Process.SYSTEM_UID), callingUid, userId);
+                if (!blockResolution && !blockNormalResolution) {
+                    final ResolveInfo ri = new ResolveInfo();
+                    ri.providerInfo = pi;
+                    list.add(ri);
+                }
+            }
+            return list;
+        }
+
+        // reader
+        synchronized (mPm.mLock) {
+            String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
+                        resolvedType, flags, userId);
+                if (resolveInfos == null) {
+                    return Collections.emptyList();
+                }
+                return applyPostContentProviderResolutionFilter(
+                        resolveInfos, instantAppPkgName, userId, callingUid);
+            }
+            final AndroidPackage pkg = mPm.mPackages.get(pkgName);
+            if (pkg != null) {
+                final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
+                        resolvedType, flags,
+                        pkg.getProviders(), userId);
+                if (resolveInfos == null) {
+                    return Collections.emptyList();
+                }
+                return applyPostContentProviderResolutionFilter(
+                        resolveInfos, instantAppPkgName, userId, callingUid);
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private List<ResolveInfo> applyPostContentProviderResolutionFilter(
+            List<ResolveInfo> resolveInfos, String instantAppPkgName,
+            @UserIdInt int userId, int callingUid) {
+        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+            final ResolveInfo info = resolveInfos.get(i);
+
+            if (instantAppPkgName == null) {
+                SettingBase callingSetting =
+                        mPm.mSettings.getSettingLPr(UserHandle.getAppId(callingUid));
+                PackageSetting resolvedSetting =
+                        mPm.getPackageSettingInternal(info.providerInfo.packageName, 0);
+                if (!mPm.mAppsFilter.shouldFilterApplication(
+                        callingUid, callingSetting, resolvedSetting, userId)) {
+                    continue;
+                }
+            }
+
+            final boolean isEphemeralApp = info.providerInfo.applicationInfo.isInstantApp();
+            // allow providers that are defined in the provided package
+            if (isEphemeralApp && instantAppPkgName.equals(info.providerInfo.packageName)) {
+                if (info.providerInfo.splitName != null
+                        && !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
+                        info.providerInfo.splitName)) {
+                    if (mPm.mInstantAppInstallerActivity == null) {
+                        if (DEBUG_INSTANT) {
+                            Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
+                        }
+                        resolveInfos.remove(i);
+                        continue;
+                    }
+                    // requested provider is defined in a split that hasn't been installed yet.
+                    // add the installer to the resolve list
+                    if (DEBUG_INSTANT) {
+                        Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
+                    }
+                    final ResolveInfo installerInfo = new ResolveInfo(
+                            mPm.getInstantAppInstallerInfo());
+                    installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
+                            null /*failureActivity*/,
+                            info.providerInfo.packageName,
+                            info.providerInfo.applicationInfo.longVersionCode,
+                            info.providerInfo.splitName);
+                    // add a non-generic filter
+                    installerInfo.filter = new IntentFilter();
+                    // load resources from the correct package
+                    installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+                    resolveInfos.set(i, installerInfo);
+                }
+                continue;
+            }
+            // allow providers that have been explicitly exposed to instant applications
+            if (!isEphemeralApp && (
+                    (info.providerInfo.flags & ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
+                continue;
+            }
+            resolveInfos.remove(i);
+        }
+        return resolveInfos;
+    }
+
+    public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
+            Intent[] specifics, String[] specificTypes, Intent intent,
+            String resolvedType, int flags, int userId) {
+        if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+        final int callingUid = Binder.getCallingUid();
+        flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+                mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
+                        flags));
+        mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+                false /*checkShell*/, "query intent activity options");
+        final String resultsAction = intent.getAction();
+
+        final List<ResolveInfo> results = mPm.queryIntentActivitiesInternal(intent, resolvedType,
+                flags | PackageManager.GET_RESOLVED_FILTER, userId);
+
+        if (DEBUG_INTENT_MATCHING) {
+            Log.v(TAG, "Query " + intent + ": " + results);
+        }
+
+        int specificsPos = 0;
+        int N;
+
+        // todo: note that the algorithm used here is O(N^2).  This
+        // isn't a problem in our current environment, but if we start running
+        // into situations where we have more than 5 or 10 matches then this
+        // should probably be changed to something smarter...
+
+        // First we go through and resolve each of the specific items
+        // that were supplied, taking care of removing any corresponding
+        // duplicate items in the generic resolve list.
+        if (specifics != null) {
+            for (int i = 0; i < specifics.length; i++) {
+                final Intent sintent = specifics[i];
+                if (sintent == null) {
+                    continue;
+                }
+
+                if (DEBUG_INTENT_MATCHING) {
+                    Log.v(TAG, "Specific #" + i + ": " + sintent);
+                }
+
+                String action = sintent.getAction();
+                if (resultsAction != null && resultsAction.equals(action)) {
+                    // If this action was explicitly requested, then don't
+                    // remove things that have it.
+                    action = null;
+                }
+
+                ResolveInfo ri = null;
+                ActivityInfo ai = null;
+
+                ComponentName comp = sintent.getComponent();
+                if (comp == null) {
+                    ri = mPm.resolveIntent(
+                            sintent,
+                            specificTypes != null ? specificTypes[i] : null,
+                            flags, userId);
+                    if (ri == null) {
+                        continue;
+                    }
+                    if (ri == mPm.mResolveInfo) {
+                        // ACK!  Must do something better with this.
+                    }
+                    ai = ri.activityInfo;
+                    comp = new ComponentName(ai.applicationInfo.packageName,
+                            ai.name);
+                } else {
+                    ai = mPm.getActivityInfo(comp, flags, userId);
+                    if (ai == null) {
+                        continue;
+                    }
+                }
+
+                // Look for any generic query activities that are duplicates
+                // of this specific one, and remove them from the results.
+                if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
+                N = results.size();
+                int j;
+                for (j = specificsPos; j < N; j++) {
+                    ResolveInfo sri = results.get(j);
+                    if ((sri.activityInfo.name.equals(comp.getClassName())
+                            && sri.activityInfo.applicationInfo.packageName.equals(
+                            comp.getPackageName()))
+                            || (action != null && sri.filter.matchAction(action))) {
+                        results.remove(j);
+                        if (DEBUG_INTENT_MATCHING) {
+                            Log.v(
+                                    TAG, "Removing duplicate item from " + j
+                                            + " due to specific " + specificsPos);
+                        }
+                        if (ri == null) {
+                            ri = sri;
+                        }
+                        j--;
+                        N--;
+                    }
+                }
+
+                // Add this specific item to its proper place.
+                if (ri == null) {
+                    ri = new ResolveInfo();
+                    ri.activityInfo = ai;
+                }
+                results.add(specificsPos, ri);
+                ri.specificIndex = i;
+                specificsPos++;
+            }
+        }
+
+        // Now we go through the remaining generic results and remove any
+        // duplicate actions that are found here.
+        N = results.size();
+        for (int i = specificsPos; i < N - 1; i++) {
+            final ResolveInfo rii = results.get(i);
+            if (rii.filter == null) {
+                continue;
+            }
+
+            // Iterate over all of the actions of this result's intent
+            // filter...  typically this should be just one.
+            final Iterator<String> it = rii.filter.actionsIterator();
+            if (it == null) {
+                continue;
+            }
+            while (it.hasNext()) {
+                final String action = it.next();
+                if (resultsAction != null && resultsAction.equals(action)) {
+                    // If this action was explicitly requested, then don't
+                    // remove things that have it.
+                    continue;
+                }
+                for (int j = i + 1; j < N; j++) {
+                    final ResolveInfo rij = results.get(j);
+                    if (rij.filter != null && rij.filter.hasAction(action)) {
+                        results.remove(j);
+                        if (DEBUG_INTENT_MATCHING) {
+                            Log.v(
+                                    TAG, "Removing duplicate item from " + j
+                                            + " due to action " + action + " at " + i);
+                        }
+                        j--;
+                        N--;
+                    }
+                }
+            }
+
+            // If the caller didn't request filter information, drop it now
+            // so we don't have to marshall/unmarshall it.
+            if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
+                rii.filter = null;
+            }
+        }
+
+        // Filter out the caller activity if so requested.
+        if (caller != null) {
+            N = results.size();
+            for (int i = 0; i < N; i++) {
+                ActivityInfo ainfo = results.get(i).activityInfo;
+                if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
+                        && caller.getClassName().equals(ainfo.name)) {
+                    results.remove(i);
+                    break;
+                }
+            }
+        }
+
+        // If the caller didn't request filter information,
+        // drop them now so we don't have to
+        // marshall/unmarshall it.
+        if ((flags & PackageManager.GET_RESOLVED_FILTER) == 0) {
+            N = results.size();
+            for (int i = 0; i < N; i++) {
+                results.get(i).filter = null;
+            }
+        }
+
+        if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
+        return results;
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index ef06c58..339c4c1 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -115,12 +115,22 @@
 /**
  * Helper class that handles package scanning logic
  */
-public final class ScanPackageHelper {
+final class ScanPackageHelper {
     final PackageManagerService mPm;
+    final InstallPackageHelper mInstallPackageHelper;
+    final PackageManagerServiceInjector mInjector;
 
     // TODO(b/198166813): remove PMS dependency
     public ScanPackageHelper(PackageManagerService pm) {
         mPm = pm;
+        mInjector = pm.mInjector;
+        mInstallPackageHelper = new InstallPackageHelper(mPm, mInjector);
+    }
+
+    ScanPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
+        mPm = pm;
+        mInjector = injector;
+        mInstallPackageHelper = new InstallPackageHelper(mPm, injector);
     }
 
     /**
@@ -197,7 +207,7 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
-        try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
+        try (PackageParser2 pp = mInjector.getScanningPackageParser()) {
             parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -271,7 +281,7 @@
                     originalPkgSetting, realPkgName, parseFlags, scanFlags,
                     Objects.equals(parsedPackage.getPackageName(), platformPackageName), user,
                     cpuAbiOverride);
-            return scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest, currentTime);
+            return scanPackageOnlyLI(request, mInjector, mPm.mFactoryTest, currentTime);
         }
     }
 
@@ -824,7 +834,7 @@
                     applyPolicy(parsedPackage, scanFlags,
                             platformPackage, true);
                     final ScanResult scanResult =
-                            scanPackageOnlyLI(request, mPm.mInjector,
+                            scanPackageOnlyLI(request, mInjector,
                                     mPm.mFactoryTest, -1L);
                     if (scanResult.mExistingSettingCopied
                             && scanResult.mRequest.mPkgSetting != null) {
@@ -858,9 +868,9 @@
                             + "; " + pkgSetting.getPathString()
                             + " --> " + parsedPackage.getPath());
 
-            final InstallArgs args = mPm.createInstallArgsForExisting(
+            final InstallArgs args = new FileInstallArgs(
                     pkgSetting.getPathString(), getAppDexInstructionSets(
-                            pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
+                            pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
             args.cleanUpResourcesLI();
             synchronized (mPm.mLock) {
                 mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
@@ -944,9 +954,10 @@
                                 + parsedPackage.getLongVersionCode()
                                 + "; " + pkgSetting.getPathString() + " --> "
                                 + parsedPackage.getPath());
-                InstallArgs args = mPm.createInstallArgsForExisting(
+                InstallArgs args = new FileInstallArgs(
                         pkgSetting.getPathString(), getAppDexInstructionSets(
-                                pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()));
+                                pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
+                        mPm);
                 synchronized (mPm.mInstallLock) {
                     args.cleanUpResourcesLI();
                 }
@@ -973,7 +984,7 @@
                 try {
                     final String pkgName = scanResult.mPkgSetting.getPackageName();
                     final Map<String, ReconciledPackage> reconcileResult =
-                            mPm.reconcilePackagesLocked(
+                            mInstallPackageHelper.reconcilePackagesLocked(
                                     new ReconcileRequest(
                                             Collections.singletonMap(pkgName, scanResult),
                                             mPm.mSharedLibraries,
@@ -985,9 +996,9 @@
                                             Collections.singletonMap(pkgName,
                                                     mPm.getSharedLibLatestVersionSetting(
                                                             scanResult))),
-                                    mPm.mSettings.getKeySetManagerService(), mPm.mInjector);
+                                    mPm.mSettings.getKeySetManagerService(), mInjector);
                     appIdCreated = optimisticallyRegisterAppId(scanResult);
-                    mPm.commitReconciledScanResultLocked(
+                    mInstallPackageHelper.commitReconciledScanResultLocked(
                             reconcileResult.get(pkgName), mPm.mUserManager.getUserIds());
                 } catch (PackageManagerException e) {
                     if (appIdCreated) {
@@ -1075,9 +1086,9 @@
         if (ps != null && !forceCollect
                 && ps.getPathString().equals(parsedPackage.getPath())
                 && ps.getLastModifiedTime() == lastModifiedTime
-                && !PackageManagerService.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
-                && !PackageManagerService.isRecoverSignatureUpdateNeeded(
-                settingsVersionForPackage)) {
+                && !InstallPackageHelper.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
+                && !InstallPackageHelper.isRecoverSignatureUpdateNeeded(
+                        settingsVersionForPackage)) {
             if (ps.getSigningDetails().getSignatures() != null
                     && ps.getSigningDetails().getSignatures().length != 0
                     && ps.getSigningDetails().getSignatureSchemeVersion()
diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
new file mode 100644
index 0000000..55e90a5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SharedLibraryHelper.java
@@ -0,0 +1,326 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.os.Build;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import libcore.util.HexEncoding;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+final class SharedLibraryHelper {
+    private static final boolean DEBUG_SHARED_LIBRARIES = false;
+
+    /**
+     * Apps targeting Android S and above need to declare dependencies to the public native
+     * shared libraries that are defined by the device maker using {@code uses-native-library} tag
+     * in its {@code AndroidManifest.xml}.
+     *
+     * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
+     * the package manager rejects to install the app. The dependency can be specified as optional
+     * using {@code android:required} attribute in the tag, in which case failing to satisfy the
+     * dependency doesn't stop the installation.
+     * <p>Once installed, an app is provided with only the native shared libraries that are
+     * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
+     * in the app manifest will fail even if it actually exists on the device.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
+
+    /**
+     * Compare the newly scanned package with current system state to see which of its declared
+     * shared libraries should be allowed to be added to the system.
+     */
+    public static List<SharedLibraryInfo> getAllowedSharedLibInfos(
+            ScanResult scanResult,
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
+        // Let's used the parsed package as scanResult.pkgSetting may be null
+        final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+        if (scanResult.mStaticSharedLibraryInfo == null
+                && scanResult.mDynamicSharedLibraryInfos == null) {
+            return null;
+        }
+
+        // Any app can add new static shared libraries
+        if (scanResult.mStaticSharedLibraryInfo != null) {
+            return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
+        }
+        final boolean hasDynamicLibraries = parsedPackage.isSystem()
+                && scanResult.mDynamicSharedLibraryInfos != null;
+        if (!hasDynamicLibraries) {
+            return null;
+        }
+        final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
+                .isUpdatedSystemApp();
+        // We may not yet have disabled the updated package yet, so be sure to grab the
+        // current setting if that's the case.
+        final PackageSetting updatedSystemPs = isUpdatedSystemApp
+                ? scanResult.mRequest.mDisabledPkgSetting == null
+                ? scanResult.mRequest.mOldPkgSetting
+                : scanResult.mRequest.mDisabledPkgSetting
+                : null;
+        if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
+                || updatedSystemPs.getPkg().getLibraryNames() == null)) {
+            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                    + " declares libraries that are not declared on the system image; skipping");
+            return null;
+        }
+        final ArrayList<SharedLibraryInfo> infos =
+                new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
+        for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
+            final String name = info.getName();
+            if (isUpdatedSystemApp) {
+                // New library entries can only be added through the
+                // system image.  This is important to get rid of a lot
+                // of nasty edge cases: for example if we allowed a non-
+                // system update of the app to add a library, then uninstalling
+                // the update would make the library go away, and assumptions
+                // we made such as through app install filtering would now
+                // have allowed apps on the device which aren't compatible
+                // with it.  Better to just have the restriction here, be
+                // conservative, and create many fewer cases that can negatively
+                // impact the user experience.
+                if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
+                    Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                            + " declares library " + name
+                            + " that is not declared on system image; skipping");
+                    continue;
+                }
+            }
+            if (sharedLibExists(
+                    name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
+                Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
+                        + name + " that already exists; skipping");
+                continue;
+            }
+            infos.add(info);
+        }
+        return infos;
+    }
+
+    public static boolean sharedLibExists(final String name, final long version,
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
+        return versionedLib != null && versionedLib.indexOfKey(version) >= 0;
+    }
+
+    /**
+     * Returns false if the adding shared library already exists in the map and so could not be
+     * added.
+     */
+    public static boolean addSharedLibraryToPackageVersionMap(
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
+            SharedLibraryInfo library) {
+        final String name = library.getName();
+        if (target.containsKey(name)) {
+            if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
+                // We've already added this non-version-specific library to the map.
+                return false;
+            } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
+                // We've already added this version of a version-specific library to the map.
+                return false;
+            }
+        } else {
+            target.put(name, new WatchedLongSparseArray<>());
+        }
+        target.get(name).put(library.getLongVersion(), library);
+        return true;
+    }
+
+    public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
+            Map<String, AndroidPackage> availablePackages,
+            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+            PlatformCompat platformCompat) throws PackageManagerException {
+        if (pkg == null) {
+            return null;
+        }
+        // The collection used here must maintain the order of addition (so
+        // that libraries are searched in the correct order) and must have no
+        // duplicates.
+        ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
+        if (!pkg.getUsesLibraries().isEmpty()) {
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
+                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), null,
+                    availablePackages, existingLibraries, newLibraries);
+        }
+        if (!pkg.getUsesStaticLibraries().isEmpty()) {
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
+                    pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
+                    pkg.getPackageName(), true, pkg.getTargetSdkVersion(), usesLibraryInfos,
+                    availablePackages, existingLibraries, newLibraries);
+        }
+        if (!pkg.getUsesOptionalLibraries().isEmpty()) {
+            usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(),
+                    null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
+                    usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+        }
+        if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
+                pkg.getPackageName(), pkg.getTargetSdkVersion())) {
+            if (!pkg.getUsesNativeLibraries().isEmpty()) {
+                usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
+                        null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
+                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+            }
+            if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
+                usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
+                        null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
+                        usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
+            }
+        }
+        return usesLibraryInfos;
+    }
+
+    public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
+            @NonNull List<String> requestedLibraries,
+            @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
+            @NonNull String packageName, boolean required, int targetSdk,
+            @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
+            @NonNull final Map<String, AndroidPackage> availablePackages,
+            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+            throws PackageManagerException {
+        final int libCount = requestedLibraries.size();
+        for (int i = 0; i < libCount; i++) {
+            final String libName = requestedLibraries.get(i);
+            final long libVersion = requiredVersions != null ? requiredVersions[i]
+                    : SharedLibraryInfo.VERSION_UNDEFINED;
+            final SharedLibraryInfo libraryInfo =
+                    getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries);
+            if (libraryInfo == null) {
+                if (required) {
+                    throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                            "Package " + packageName + " requires unavailable shared library "
+                                    + libName + "; failing!");
+                } else if (DEBUG_SHARED_LIBRARIES) {
+                    Slog.i(TAG, "Package " + packageName
+                            + " desires unavailable shared library "
+                            + libName + "; ignoring!");
+                }
+            } else {
+                if (requiredVersions != null && requiredCertDigests != null) {
+                    if (libraryInfo.getLongVersion() != requiredVersions[i]) {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable static shared"
+                                        + " library " + libName + " version "
+                                        + libraryInfo.getLongVersion() + "; failing!");
+                    }
+                    AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
+                    SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
+                    if (libPkg == null) {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable static shared"
+                                        + " library; failing!");
+                    }
+                    final String[] expectedCertDigests = requiredCertDigests[i];
+                    if (expectedCertDigests.length > 1) {
+                        // For apps targeting O MR1 we require explicit enumeration of all certs.
+                        final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
+                                ? PackageUtils.computeSignaturesSha256Digests(
+                                libPkg.getSignatures())
+                                : PackageUtils.computeSignaturesSha256Digests(
+                                        new Signature[]{libPkg.getSignatures()[0]});
+
+                        // Take a shortcut if sizes don't match. Note that if an app doesn't
+                        // target O we don't parse the "additional-certificate" tags similarly
+                        // how we only consider all certs only for apps targeting O (see above).
+                        // Therefore, the size check is safe to make.
+                        if (expectedCertDigests.length != libCertDigests.length) {
+                            throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                    "Package " + packageName + " requires differently signed"
+                                            + " static shared library; failing!");
+                        }
+
+                        // Use a predictable order as signature order may vary
+                        Arrays.sort(libCertDigests);
+                        Arrays.sort(expectedCertDigests);
+
+                        final int certCount = libCertDigests.length;
+                        for (int j = 0; j < certCount; j++) {
+                            if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
+                                throw new PackageManagerException(
+                                        INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                        "Package " + packageName + " requires differently signed"
+                                                + " static shared library; failing!");
+                            }
+                        }
+                    } else {
+                        // lib signing cert could have rotated beyond the one expected, check to see
+                        // if the new one has been blessed by the old
+                        byte[] digestBytes = HexEncoding.decode(
+                                expectedCertDigests[0], false /* allowSingleChar */);
+                        if (!libPkg.hasSha256Certificate(digestBytes)) {
+                            throw new PackageManagerException(
+                                    INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                    "Package " + packageName + " requires differently signed"
+                                            + " static shared library; failing!");
+                        }
+                    }
+                }
+                if (outUsedLibraries == null) {
+                    outUsedLibraries = new ArrayList<>();
+                }
+                outUsedLibraries.add(libraryInfo);
+            }
+        }
+        return outUsedLibraries;
+    }
+
+    @Nullable
+    public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+            @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
+        if (newLibraries != null) {
+            final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
+            SharedLibraryInfo info = null;
+            if (versionedLib != null) {
+                info = versionedLib.get(version);
+            }
+            if (info != null) {
+                return info;
+            }
+        }
+        final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
+        if (versionedLib == null) {
+            return null;
+        }
+        return versionedLib.get(version);
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index de5e6c54..193b27d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,7 +47,6 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -3306,17 +3305,7 @@
         final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
                 || !transitionStarted;
         mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
-        if (!showing) {
-            return false;
-        }
-        if (mKeyguardCandidate != null) {
-            if (isOccluded) {
-                mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
-            } else if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
-                mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
-            }
-        }
-        return true;
+        return showing;
     }
 
     /** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 8978fab..0080ec6 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -234,13 +234,6 @@
         return false;
     }
 
-    public boolean hasLockscreenWallpaper() {
-        if (mKeyguardService != null) {
-            return mKeyguardService.hasLockscreenWallpaper();
-        }
-        return false;
-    }
-
     public boolean hasKeyguard() {
         return mKeyguardState.deviceHasKeyguard;
     }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index c356fec..2029f86 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -267,10 +267,6 @@
         return mKeyguardStateMonitor.isTrusted();
     }
 
-    public boolean hasLockscreenWallpaper() {
-        return mKeyguardStateMonitor.hasLockscreenWallpaper();
-    }
-
     public boolean isSecure(int userId) {
         return mKeyguardStateMonitor.isSecure(userId);
     }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index f0f62ed..c0aa8ae 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -44,7 +44,6 @@
     private volatile boolean mSimSecure = true;
     private volatile boolean mInputRestricted = true;
     private volatile boolean mTrusted = false;
-    private volatile boolean mHasLockscreenWallpaper = false;
 
     private int mCurrentUserId;
 
@@ -79,10 +78,6 @@
         return mTrusted;
     }
 
-    public boolean hasLockscreenWallpaper() {
-        return mHasLockscreenWallpaper;
-    }
-
     public int getCurrentUser() {
         return mCurrentUserId;
     }
@@ -116,11 +111,6 @@
         mCallback.onTrustedChanged();
     }
 
-    @Override // Binder interface
-    public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
-        mHasLockscreenWallpaper = hasLockscreenWallpaper;
-    }
-
     public interface StateCallback {
         void onTrustedChanged();
         void onShowingChanged();
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 0bfb55c..f45bda7 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -47,11 +47,13 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Collections;
@@ -302,49 +304,53 @@
     public String computePackageStateHash(@UserIdInt int userId) {
         PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
-        final MessageDigestUtils md = new MessageDigestUtils();
+        final MessageDigestOutputStream mdos = new MessageDigestOutputStream();
 
+        DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(mdos));
         packageManagerInternal.forEachInstalledPackage(pkg -> {
-            md.writeString(pkg.getPackageName());
-            md.writeLong(pkg.getLongVersionCode());
-            md.writeInt(packageManagerInternal.getApplicationEnabledState(pkg.getPackageName(),
-                    userId));
+            try {
+                dataOutputStream.writeUTF(pkg.getPackageName());
+                dataOutputStream.writeLong(pkg.getLongVersionCode());
+                dataOutputStream.writeInt(packageManagerInternal.getApplicationEnabledState(
+                        pkg.getPackageName(), userId));
 
-            final List<String> requestedPermissions = pkg.getRequestedPermissions();
-            final int requestedPermissionsSize = requestedPermissions.size();
-            md.writeInt(requestedPermissionsSize);
-            for (int i = 0; i < requestedPermissionsSize; i++) {
-                md.writeString(requestedPermissions.get(i));
-            }
+                final List<String> requestedPermissions = pkg.getRequestedPermissions();
+                final int requestedPermissionsSize = requestedPermissions.size();
+                dataOutputStream.writeInt(requestedPermissionsSize);
+                for (int i = 0; i < requestedPermissionsSize; i++) {
+                    dataOutputStream.writeUTF(requestedPermissions.get(i));
+                }
 
-            final ArraySet<String> enabledComponents = packageManagerInternal.getEnabledComponents(
-                    pkg.getPackageName(), userId);
-            final int enabledComponentsSize = CollectionUtils.size(enabledComponents);
-            md.writeInt(enabledComponentsSize);
-            for (int i = 0; i < enabledComponentsSize; i++) {
-                md.writeString(enabledComponents.valueAt(i));
-            }
+                final ArraySet<String> enabledComponents =
+                        packageManagerInternal.getEnabledComponents(pkg.getPackageName(), userId);
+                final int enabledComponentsSize = CollectionUtils.size(enabledComponents);
+                dataOutputStream.writeInt(enabledComponentsSize);
+                for (int i = 0; i < enabledComponentsSize; i++) {
+                    dataOutputStream.writeUTF(enabledComponents.valueAt(i));
+                }
 
-            final ArraySet<String> disabledComponents =
-                    packageManagerInternal.getDisabledComponents(pkg.getPackageName(), userId);
-            final int disabledComponentsSize = CollectionUtils.size(disabledComponents);
-            for (int i = 0; i < disabledComponentsSize; i++) {
-                md.writeString(disabledComponents.valueAt(i));
-            }
+                final ArraySet<String> disabledComponents =
+                        packageManagerInternal.getDisabledComponents(pkg.getPackageName(), userId);
+                final int disabledComponentsSize = CollectionUtils.size(disabledComponents);
+                for (int i = 0; i < disabledComponentsSize; i++) {
+                    dataOutputStream.writeUTF(disabledComponents.valueAt(i));
+                }
 
-            for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
-                md.writeBytes(signature.toByteArray());
+                for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
+                    dataOutputStream.write(signature.toByteArray());
+                }
+            } catch (IOException e) {
+                // Never happens for MessageDigestOutputStream and DataOutputStream.
+                throw new AssertionError(e);
             }
         }, userId);
-
-        return md.getDigestAsString();
+        return mdos.getDigestAsString();
     }
 
-    private static class MessageDigestUtils {
-        private final byte[] mBuffer = new byte[8];
+    private static class MessageDigestOutputStream extends OutputStream {
         private final MessageDigest mMessageDigest;
 
-        MessageDigestUtils() {
+        MessageDigestOutputStream() {
             try {
                 mMessageDigest = MessageDigest.getInstance("SHA256");
             } catch (NoSuchAlgorithmException e) {
@@ -358,32 +364,19 @@
             return HexEncoding.encodeToString(mMessageDigest.digest(), true /* uppercase */);
         }
 
-        void writeBytes(@NonNull byte[] bytes) {
-            mMessageDigest.update(bytes);
+        @Override
+        public void write(int b) throws IOException {
+            mMessageDigest.update((byte) b);
         }
 
-        void writeString(@NonNull String s) {
-            mMessageDigest.update(s.getBytes(StandardCharsets.UTF_8));
+        @Override
+        public void write(byte[] b) throws IOException {
+            mMessageDigest.update(b);
         }
 
-        void writeLong(long v) {
-            mBuffer[0] = (byte) (v >>> 56);
-            mBuffer[1] = (byte) (v >>> 48);
-            mBuffer[2] = (byte) (v >>> 40);
-            mBuffer[3] = (byte) (v >>> 32);
-            mBuffer[4] = (byte) (v >>> 24);
-            mBuffer[5] = (byte) (v >>> 16);
-            mBuffer[6] = (byte) (v >>>  8);
-            mBuffer[7] = (byte) (v >>>  0);
-            mMessageDigest.update(mBuffer, 0, 8);
-        }
-
-        void writeInt(int v) {
-            mBuffer[0] = (byte) (v >>> 24);
-            mBuffer[1] = (byte) (v >>> 16);
-            mBuffer[2] = (byte) (v >>>  8);
-            mBuffer[3] = (byte) (v >>>  0);
-            mMessageDigest.update(mBuffer, 0, 4);
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            mMessageDigest.update(b, off, len);
         }
     }
 }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index db408488..96823c8a 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -1088,6 +1088,10 @@
         @GuardedBy("mSamples")
         private long mLastForecastCallTimeMillis = 0;
 
+        private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
+        @VisibleForTesting
+        long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
+
         void updateSevereThresholds() {
             synchronized (mSamples) {
                 List<TemperatureThreshold> thresholds =
@@ -1107,13 +1111,12 @@
             }
         }
 
-        private static final int INACTIVITY_THRESHOLD_MILLIS = 10000;
         private static final int RING_BUFFER_SIZE = 30;
 
         private void updateTemperature() {
             synchronized (mSamples) {
                 if (SystemClock.elapsedRealtime() - mLastForecastCallTimeMillis
-                        < INACTIVITY_THRESHOLD_MILLIS) {
+                        < mInactivityThresholdMillis) {
                     // Trigger this again after a second as long as forecast has been called more
                     // recently than the inactivity timeout
                     mHandler.postDelayed(this::updateTemperature, 1000);
@@ -1199,7 +1202,7 @@
 
         float getForecast(int forecastSeconds) {
             synchronized (mSamples) {
-                mLastForecastCallTimeMillis = System.currentTimeMillis();
+                mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
                 if (mSamples.isEmpty()) {
                     updateTemperature();
                 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 0f1563b..720c773 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1162,6 +1162,7 @@
         assertInWorkerThread();
         Slog.i(TAG, "makeRollbackAvailable id=" + rollback.info.getRollbackId());
         rollback.makeAvailable();
+        mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
 
         // TODO(zezeozue): Provide API to explicitly start observing instead
         // of doing this for all rollbacks. If we do this for all rollbacks,
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1295b70..1856786 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -51,6 +51,7 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
@@ -75,8 +76,11 @@
     private final Handler mHandler;
     private final ApexManager mApexManager;
     private final File mLastStagedRollbackIdsFile;
+    private final File mTwoPhaseRollbackEnabledFile;
     // Staged rollback ids that have been committed but their session is not yet ready
     private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
+    // True if needing to roll back only rebootless apexes when native crash happens
+    private boolean mTwoPhaseRollbackEnabled;
 
     RollbackPackageHealthObserver(Context context) {
         mContext = context;
@@ -86,8 +90,19 @@
         File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
         dataDir.mkdirs();
         mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
+        mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
         PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
         mApexManager = ApexManager.getInstance();
+
+        if (SystemProperties.getBoolean("sys.boot_completed", false)) {
+            // Load the value from the file if system server has crashed and restarted
+            mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
+        } else {
+            // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
+            // installed before reboot is stable if native crash didn't happen.
+            mTwoPhaseRollbackEnabled = false;
+            writeBoolean(mTwoPhaseRollbackEnabledFile, false);
+        }
     }
 
     @Override
@@ -144,6 +159,31 @@
         PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
     }
 
+    @AnyThread
+    void notifyRollbackAvailable(RollbackInfo rollback) {
+        mHandler.post(() -> {
+            // Enable two-phase rollback when a rebootless apex rollback is made available.
+            // We assume the rebootless apex is stable and is less likely to be the cause
+            // if native crash doesn't happen before reboot. So we will clear the flag and disable
+            // two-phase rollback after reboot.
+            if (isRebootlessApex(rollback)) {
+                mTwoPhaseRollbackEnabled = true;
+                writeBoolean(mTwoPhaseRollbackEnabledFile, true);
+            }
+        });
+    }
+
+    private static boolean isRebootlessApex(RollbackInfo rollback) {
+        if (!rollback.isStaged()) {
+            for (PackageRollbackInfo info : rollback.getPackages()) {
+                if (info.isApex()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
      * to check for native crashes and mitigate them if needed.
      */
@@ -155,6 +195,7 @@
     @WorkerThread
     private void onBootCompleted() {
         assertInWorkerThread();
+
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
             // TODO(gavincorkery): Call into Package Watchdog from outside the observer
@@ -277,6 +318,23 @@
         return mPendingStagedRollbackIds.isEmpty();
     }
 
+    private static boolean readBoolean(File file) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return fis.read() == 1;
+        } catch (IOException ignore) {
+            return false;
+        }
+    }
+
+    private static void writeBoolean(File file, boolean value) {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(value ? 1 : 0);
+            fos.flush();
+            FileUtils.sync(fos);
+        } catch (IOException ignore) {
+        }
+    }
+
     @WorkerThread
     private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
         assertInWorkerThread();
@@ -420,13 +478,44 @@
                 Collections.singletonList(failedPackage), rollbackReceiver.getIntentSender());
     }
 
+    /**
+     * Two-phase rollback:
+     * 1. roll back rebootless apexes first
+     * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
+     *
+     * This approach gives us a better chance to correctly attribute native crash to rebootless
+     * apex update without rolling back Mainline updates which might contains critical security
+     * fixes.
+     */
+    @WorkerThread
+    private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
+        assertInWorkerThread();
+        if (!mTwoPhaseRollbackEnabled) {
+            return false;
+        }
+
+        Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
+        boolean found = false;
+        for (RollbackInfo rollback : rollbacks) {
+            if (isRebootlessApex(rollback)) {
+                VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
+                rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+                found = true;
+            }
+        }
+        return found;
+    }
+
     @WorkerThread
     private void rollbackAll() {
         assertInWorkerThread();
-        Slog.i(TAG, "Rolling back all available rollbacks");
         RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+        if (useTwoPhaseRollback(rollbacks)) {
+            return;
+        }
 
+        Slog.i(TAG, "Rolling back all available rollbacks");
         // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
         // pending staged rollbacks are handled.
         for (RollbackInfo rollback : rollbacks) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 9ac7e3b..9f211db 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -310,6 +310,7 @@
         for (int i = 0; i < aidlEvent.data.length; ++i) {
             aidlEvent.data[i] = hidlEvent.data.get(i);
         }
+        aidlEvent.recognitionStillActive = aidlEvent.status == RecognitionStatus.FORCED;
         return aidlEvent;
     }
 
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index e3ce719..990b21c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -226,8 +226,7 @@
         public void recognitionCallback(int modelHandle, RecognitionEvent event) {
             // A recognition event must be the last one for its model, unless it is a forced one
             // (those leave the model active).
-            mCallbackThread.pushWithDedupe(modelHandle,
-                    event.status != RecognitionStatus.FORCED,
+            mCallbackThread.pushWithDedupe(modelHandle, !event.recognitionStillActive,
                     () -> mDelegateCallback.recognitionCallback(modelHandle, event));
         }
 
@@ -235,8 +234,7 @@
         public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
             // A recognition event must be the last one for its model, unless it is a forced one
             // (those leave the model active).
-            mCallbackThread.pushWithDedupe(modelHandle,
-                    event.common.status != RecognitionStatus.FORCED,
+            mCallbackThread.pushWithDedupe(modelHandle, !event.common.recognitionStillActive,
                     () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
         }
 
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index 6870f4f..235d10f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -55,8 +55,7 @@
     private final ISoundTriggerHal mUnderlying;
     private final Map<Integer, ModelState> mModelStates = new HashMap<>();
 
-    public SoundTriggerHalEnforcer(
-            ISoundTriggerHal underlying) {
+    public SoundTriggerHalEnforcer(ISoundTriggerHal underlying) {
         mUnderlying = underlying;
     }
 
@@ -239,15 +238,12 @@
     private class ModelCallbackEnforcer implements ModelCallback {
         private final ModelCallback mUnderlying;
 
-        private ModelCallbackEnforcer(
-                ModelCallback underlying) {
+        private ModelCallbackEnforcer(ModelCallback underlying) {
             mUnderlying = underlying;
         }
 
         @Override
         public void recognitionCallback(int model, RecognitionEvent event) {
-            int status = event.status;
-
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -255,7 +251,15 @@
                     reboot();
                     return;
                 }
-                if (status != RecognitionStatus.FORCED) {
+                if (event.recognitionStillActive && event.status != RecognitionStatus.SUCCESS
+                        && event.status != RecognitionStatus.FORCED) {
+                    Log.wtfStack(TAG,
+                            "recognitionStillActive is only allowed when the recognition status "
+                                    + "is SUCCESS");
+                    reboot();
+                    return;
+                }
+                if (!event.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
@@ -265,7 +269,6 @@
 
         @Override
         public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
-            int status = event.common.status;
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null || state == ModelState.INACTIVE) {
@@ -273,7 +276,16 @@
                     reboot();
                     return;
                 }
-                if (status != RecognitionStatus.FORCED) {
+                if (event.common.recognitionStillActive
+                        && event.common.status != RecognitionStatus.SUCCESS
+                        && event.common.status != RecognitionStatus.FORCED) {
+                    Log.wtfStack(TAG,
+                            "recognitionStillActive is only allowed when the recognition status "
+                                    + "is SUCCESS");
+                    reboot();
+                    return;
+                }
+                if (!event.common.recognitionStillActive) {
                     mModelStates.replace(model, ModelState.INACTIVE);
                 }
             }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index f564756..0a085ba 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -26,6 +26,7 @@
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.os.IBinder;
@@ -221,11 +222,15 @@
 
         @Override
         public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+            // A FORCED status implies that recognition is still active after the event.
+            event.common.recognitionStillActive |= event.common.status == RecognitionStatus.FORCED;
             mDelegate.phraseRecognitionCallback(model, event);
         }
 
         @Override
         public void recognitionCallback(int model, RecognitionEvent event) {
+            // A FORCED status implies that recognition is still active after the event.
+            event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
             mDelegate.recognitionCallback(model, event);
         }
     }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 7b31946..4243fc7 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -731,7 +731,7 @@
                     int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (event.status != RecognitionStatus.FORCED) {
+                    if (!event.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -760,7 +760,7 @@
                     @NonNull PhraseRecognitionEvent event, int captureSession) {
                 synchronized (SoundTriggerMiddlewareValidation.this) {
                     ModelState modelState = mLoadedModels.get(modelHandle);
-                    if (event.common.status != RecognitionStatus.FORCED) {
+                    if (!event.common.recognitionStillActive) {
                         modelState.activityState = ModelState.Activity.LOADED;
                     }
                 }
@@ -772,7 +772,7 @@
                     Log.w(TAG, "Client callback exception.", e);
                     synchronized (SoundTriggerMiddlewareValidation.this) {
                         ModelState modelState = mLoadedModels.get(modelHandle);
-                        if (event.common.status != RecognitionStatus.FORCED) {
+                        if (!event.common.recognitionStillActive) {
                             modelState.activityState = ModelState.Activity.INTERCEPTED;
                             // If we failed to deliver an actual event to the client, they would
                             // never know to restart it whenever circumstances change. Thus, we
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index f211158..934b0e4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -24,7 +24,6 @@
 import android.media.soundtrigger.Properties;
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.RecognitionStatus;
 import android.media.soundtrigger.SoundModel;
 import android.media.soundtrigger.Status;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
@@ -461,7 +460,7 @@
                     @NonNull RecognitionEvent recognitionEvent) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (recognitionEvent.status != RecognitionStatus.FORCED) {
+                    if (!recognitionEvent.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
@@ -482,7 +481,7 @@
                     @NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
                 ISoundTriggerCallback callback;
                 synchronized (SoundTriggerModule.this) {
-                    if (phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
+                    if (!phraseRecognitionEvent.common.recognitionStillActive) {
                         setState(ModelState.LOADED);
                     }
                     callback = mCallback;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 8fb81fa..3177fac 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -37,6 +37,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 
@@ -44,6 +45,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -68,6 +71,8 @@
 
     // Map of the current available frontend resources
     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
+    // Backup Map of the current available frontend resources
+    private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
     // Map of the current available lnb resources
     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
     // Map of the current available Cas resources
@@ -174,6 +179,27 @@
         }
 
         @Override
+        public boolean hasUnusedFrontend(int frontendType) {
+            enforceTrmAccessPermission("hasUnusedFrontend");
+            synchronized (mLock) {
+                return hasUnusedFrontendInternal(frontendType);
+            }
+        }
+
+        @Override
+        public boolean isLowestPriority(int clientId, int frontendType)
+                throws RemoteException {
+            enforceTrmAccessPermission("isLowestPriority");
+            synchronized (mLock) {
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("isLowestPriority called from unregistered client: "
+                            + clientId);
+                }
+                return isLowestPriorityInternal(clientId, frontendType);
+            }
+        }
+
+        @Override
         public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
             enforceTrmAccessPermission("setFrontendInfoList");
             if (infos == null) {
@@ -458,6 +484,104 @@
                 return isHigherPriorityInternal(challengerProfile, holderProfile);
             }
         }
+
+        @Override
+        public void storeResourceMap(int resourceType) {
+            enforceTrmAccessPermission("storeResourceMap");
+            synchronized (mLock) {
+                storeResourceMapInternal(resourceType);
+            }
+        }
+
+        @Override
+        public void clearResourceMap(int resourceType) {
+            enforceTrmAccessPermission("clearResourceMap");
+            synchronized (mLock) {
+                clearResourceMapInternal(resourceType);
+            }
+        }
+
+        @Override
+        public void restoreResourceMap(int resourceType) {
+            enforceTrmAccessPermission("restoreResourceMap");
+            synchronized (mLock) {
+                restoreResourceMapInternal(resourceType);
+            }
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+
+            synchronized (mLock) {
+                if (mClientProfiles != null) {
+                    pw.println("ClientProfiles:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mFrontendResources != null) {
+                    pw.println("FrontendResources:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, FrontendResource> entry
+                            : mFrontendResources.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mFrontendResourcesBackup != null) {
+                    pw.println("FrontendResourcesBackUp:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, FrontendResource> entry
+                            : mFrontendResourcesBackup.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mLnbResources != null) {
+                    pw.println("LnbResources:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mCasResources != null) {
+                    pw.println("CasResources:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mCiCamResources != null) {
+                    pw.println("CiCamResources:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+
+                if (mListeners != null) {
+                    pw.println("Listners:");
+                    pw.increaseIndent();
+                    for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry
+                            : mListeners.entrySet()) {
+                        pw.println(entry.getKey() + " : " + entry.getValue());
+                    }
+                    pw.decreaseIndent();
+                }
+            }
+        }
+
     }
 
     /**
@@ -552,6 +676,83 @@
         return true;
     }
 
+
+    protected boolean hasUnusedFrontendInternal(int frontendType) {
+        for (FrontendResource fr : getFrontendResources().values()) {
+            if (fr.getType() == frontendType && !fr.isInUse()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isLowestPriorityInternal(int clientId, int frontendType)
+            throws RemoteException {
+        // Update the client priority
+        ClientProfile requestClient = getClientProfile(clientId);
+        if (requestClient == null) {
+            return true;
+        }
+        clientPriorityUpdateOnRequest(requestClient);
+        int clientPriority = requestClient.getPriority();
+
+        // Check if there is another holder with lower priority
+        for (FrontendResource fr : getFrontendResources().values()) {
+            if (fr.getType() == frontendType && fr.isInUse()) {
+                int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
+                // Returns false only when the clientPriority is strictly greater
+                // because false means that there is another reclaimable resource
+                if (clientPriority > priority) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    protected void storeResourceMapInternal(int resourceType) {
+        switch (resourceType) {
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+                if (mFrontendResources != null && mFrontendResources.size() > 0) {
+                    mFrontendResourcesBackup.putAll(mFrontendResources);
+                    mFrontendResources.clear();
+                }
+                break;
+                // TODO: implement for other resource type when needed
+            default:
+                break;
+        }
+    }
+
+    protected void clearResourceMapInternal(int resourceType) {
+        switch (resourceType) {
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+                if (mFrontendResources != null) {
+                    mFrontendResources.clear();
+                }
+                break;
+                // TODO: implement for other resource type when needed
+            default:
+                break;
+        }
+    }
+
+    protected void restoreResourceMapInternal(int resourceType) {
+        switch (resourceType) {
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+                if (mFrontendResourcesBackup != null
+                        && mFrontendResourcesBackup.size() > 0) {
+                    mFrontendResources.clear();
+                    mFrontendResources.putAll(mFrontendResourcesBackup);
+                    mFrontendResourcesBackup.clear();
+                }
+                break;
+                // TODO: implement for other resource type when needed
+            default:
+                break;
+        }
+    }
+
     @VisibleForTesting
     protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) {
         if (DEBUG) {
@@ -692,7 +893,7 @@
                 } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
                     // Record the frontend id with the lowest client priority among all the
                     // in use frontends when no available frontend has been found.
-                    int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
+                    int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrHandle = fr.getHandle();
                         currentLowestPriority = priority;
@@ -1168,6 +1369,33 @@
         return profile.getPriority();
     }
 
+    /**
+     * Update the owner and sharee clients' priority and get the highest priority
+     * for frontend resource
+     *
+     * @param clientId the owner client id.
+     * @return the highest priority among all the clients holding the same frontend resource.
+     */
+    private int getFrontendHighestClientPriority(int clientId) {
+        // Check if the owner profile exists
+        ClientProfile ownerClient = getClientProfile(clientId);
+        if (ownerClient == null) {
+            return 0;
+        }
+
+        // Update and get the priority of the owner client
+        int highestPriority = updateAndGetOwnerClientPriority(clientId);
+
+        // Update and get all the client IDs of frontend resource holders
+        for (int shareeId : ownerClient.getShareFeClientIds()) {
+            int priority = updateAndGetOwnerClientPriority(shareeId);
+            if (priority > highestPriority) {
+                highestPriority = priority;
+            }
+        }
+        return highestPriority;
+    }
+
     @VisibleForTesting
     @Nullable
     protected FrontendResource getFrontendResource(int frontendHandle) {
@@ -1345,6 +1573,9 @@
     }
 
     private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+        if (profile == null) {
+            return;
+        }
         // Clear Lnb
         for (Integer lnbHandle : profile.getInUseLnbHandles()) {
             getLnbResource(lnbHandle).removeOwner();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d084b13..9cc7f42 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4628,6 +4628,7 @@
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
         }
+        logAppCompatState();
     }
 
     /**
@@ -4654,7 +4655,6 @@
                 ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
         mTaskSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
         mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
-        logAppCompatState();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d944c58..dbe9cd6 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2771,9 +2771,11 @@
         // If it exist, we need to reparent target root task from TDA to launch root task.
         final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
         final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
-                mTargetRootTask.getActivityType(), null /** options */, null /** sourceTask */,
-                0 /** launchFlags */);
-        if (launchRootTask != null && launchRootTask != mTargetRootTask) {
+                mTargetRootTask.getActivityType(), null /** options */,
+                mSourceRootTask, 0 /** launchFlags */);
+        // If target root task is created by organizer, let organizer handle reparent itself.
+        if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
+                && launchRootTask != mTargetRootTask) {
             mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
             mTargetRootTask = launchRootTask;
         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index bd08d01..7817f54 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -81,7 +81,6 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -97,7 +96,6 @@
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -126,9 +124,6 @@
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
 import android.view.animation.ScaleAnimation;
 import android.view.animation.TranslateAnimation;
 
@@ -151,25 +146,13 @@
 // made visible or hidden at the next transition.
 public class AppTransition implements Dump {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM;
-    private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8;
-
-    /** Fraction of animation at which the recents thumbnail stays completely transparent */
-    private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
-    /** Fraction of animation at which the recents thumbnail becomes completely transparent */
-    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
 
     static final int DEFAULT_APP_TRANSITION_DURATION = 336;
 
-    /** Interpolator to be used for animations that respond directly to a touch */
-    static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
-            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
     /**
      * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
      * involved, to make it more understandable.
      */
-    private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
-    private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
     private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
     static final int MAX_APP_TRANSITION_DURATION = 3 * 1000; // 3 secs.
 
@@ -225,11 +208,6 @@
     private boolean mNextAppTransitionAnimationsSpecsPending;
     private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
 
-    private Rect mNextAppTransitionInsets = new Rect();
-
-    private Rect mTmpFromClipRect = new Rect();
-    private Rect mTmpToClipRect = new Rect();
-
     private final Rect mTmpRect = new Rect();
 
     private final static int APP_STATE_IDLE = 0;
@@ -238,29 +216,11 @@
     private final static int APP_STATE_TIMEOUT = 3;
     private int mAppTransitionState = APP_STATE_IDLE;
 
-    private final int mConfigShortAnimTime;
-    private final Interpolator mDecelerateInterpolator;
-    private final Interpolator mThumbnailFadeInInterpolator;
-    private final Interpolator mThumbnailFadeOutInterpolator;
-    private final Interpolator mLinearOutSlowInInterpolator;
-    private final Interpolator mFastOutLinearInInterpolator;
-    private final Interpolator mFastOutSlowInInterpolator;
-    private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
-
-    private final int mClipRevealTranslationY;
-
-    private int mCurrentUserId = 0;
-    private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
-
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
     private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener;
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
 
-    private int mLastClipRevealMaxTranslation;
-    private boolean mLastHadClipReveal;
-
     private final boolean mGridLayoutRecentsEnabled;
-    private final boolean mLowRamRecentsEnabled;
 
     private final int mDefaultWindowAnimationStyleResId;
     private boolean mOverrideTaskTransition;
@@ -276,43 +236,8 @@
         mHandler = new Handler(service.mH.getLooper());
         mDisplayContent = displayContent;
         mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.fast_out_linear_in);
-        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.fast_out_slow_in);
-        mConfigShortAnimTime = context.getResources().getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
-        mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
-                com.android.internal.R.interpolator.decelerate_cubic);
-        mThumbnailFadeInInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float input) {
-                // Linear response for first fraction, then complete after that.
-                if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
-                    return 0f;
-                }
-                float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION) /
-                        (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
-                return mFastOutLinearInInterpolator.getInterpolation(t);
-            }
-        };
-        mThumbnailFadeOutInterpolator = new Interpolator() {
-            @Override
-            public float getInterpolation(float input) {
-                // Linear response for first fraction, then complete after that.
-                if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
-                    float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
-                    return mLinearOutSlowInInterpolator.getInterpolation(t);
-                }
-                return 1f;
-            }
-        };
-        mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP
-                * mContext.getResources().getDisplayMetrics().density);
+
         mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
-        mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
 
         final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
                 com.android.internal.R.styleable.Window);
@@ -421,9 +346,6 @@
         if (!isRunning()) {
             setAppTransitionState(APP_STATE_IDLE);
             notifyAppTransitionPendingLocked();
-            mLastHadClipReveal = false;
-            mLastClipRevealMaxTranslation = 0;
-            mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
             return true;
         }
         return false;
@@ -602,21 +524,6 @@
         }
     }
 
-    void getNextAppTransitionStartRect(WindowContainer container, Rect rect) {
-        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
-                container.hashCode());
-        if (spec == null) {
-            spec = mDefaultNextAppTransitionAnimationSpec;
-        }
-        if (spec == null || spec.rect == null) {
-            Slog.e(TAG, "Starting rect for container: " + container
-                            + " requested, but not available", new Throwable());
-            rect.setEmpty();
-        } else {
-            rect.set(spec.rect);
-        }
-    }
-
     private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
             HardwareBuffer buffer) {
         mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
@@ -624,49 +531,6 @@
     }
 
     /**
-     * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
-     * the start rect is outside of the target rect, and there is a lot of movement going on.
-     *
-     * @param cutOff whether the start rect was not fully contained by the end rect
-     * @param translationX the total translation the surface moves in x direction
-     * @param translationY the total translation the surfaces moves in y direction
-     * @param displayFrame our display frame
-     *
-     * @return the duration of the clip reveal animation, in milliseconds
-     */
-    private long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
-            float translationY, Rect displayFrame) {
-        if (!cutOff) {
-            return DEFAULT_APP_TRANSITION_DURATION;
-        }
-        final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
-                Math.abs(translationY) / displayFrame.height());
-        return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction *
-                (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
-    }
-
-    /**
-     * Prepares the specified animation with a standard duration, interpolator, etc.
-     */
-    Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
-        // Pick the desired duration.  If this is an inter-activity transition,
-        // it  is the standard duration for that.  Otherwise we use the longer
-        // task transition duration.
-        final int duration;
-        switch (transit) {
-            case TRANSIT_OLD_ACTIVITY_OPEN:
-            case TRANSIT_OLD_ACTIVITY_CLOSE:
-                duration = mConfigShortAnimTime;
-                break;
-            default:
-                duration = DEFAULT_APP_TRANSITION_DURATION;
-                break;
-        }
-        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
-                mDecelerateInterpolator);
-    }
-
-    /**
      * Creates an overlay with a background color and a thumbnail for the cross profile apps
      * animation.
      */
@@ -694,32 +558,6 @@
                 mNextAppTransitionScaleUp);
     }
 
-    private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
-        return new TranslateAnimation(fromX, toX, fromY, toY);
-    }
-
-    private long getAspectScaleDuration() {
-        return THUMBNAIL_APP_TRANSITION_DURATION;
-    }
-
-    private Interpolator getAspectScaleInterpolator() {
-        return TOUCH_RESPONSE_INTERPOLATOR;
-    }
-
-    private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
-            @Nullable Rect surfaceInsets, WindowContainer container) {
-        getNextAppTransitionStartRect(container, mTmpRect);
-        return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
-                true);
-    }
-
-    private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
-            @Nullable Rect surfaceInsets, WindowContainer container) {
-        getNextAppTransitionStartRect(container, mTmpRect);
-        return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
-                false);
-    }
-
     private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
             Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
         final float sourceWidth = sourceFrame.width();
@@ -755,48 +593,6 @@
     }
 
     /**
-     * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
-     * when a thumbnail is specified with the pending animation override.
-     */
-    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
-            HardwareBuffer thumbnailHeader) {
-        Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpRect);
-        final int thumbWidthI = thumbnailHeader.getWidth();
-        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = thumbnailHeader.getHeight();
-        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
-
-        if (mNextAppTransitionScaleUp) {
-            // Animation for the thumbnail zooming from its initial size to the full screen
-            float scaleW = appWidth / thumbWidth;
-            float scaleH = appHeight / thumbHeight;
-            Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                    TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
-                    TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
-            scale.setInterpolator(mDecelerateInterpolator);
-
-            Animation alpha = new AlphaAnimation(1, 0);
-            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
-
-            // This AnimationSet uses the Interpolators assigned above.
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            set.addAnimation(alpha);
-            a = set;
-        } else {
-            // Animation for the thumbnail zooming down from the full screen to its final size
-            float scaleW = appWidth / thumbWidth;
-            float scaleH = appHeight / thumbHeight;
-            a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
-                    TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
-        }
-
-        return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
-    }
-
-    /**
      * @return true if and only if the first frame of the transition can be skipped, i.e. the first
      *         frame of the transition doesn't change the visuals on screen, so we can start
      *         directly with the second one
@@ -1545,10 +1341,6 @@
         }
     }
 
-    public void setCurrentUser(int newUserId) {
-        mCurrentUserId = newUserId;
-    }
-
     boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
         if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
             return false;
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 98757f0..7485a1e 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -272,6 +272,10 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
+        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            return;
+        }
+
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(NAME, mName);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 166d9b9..54078c4 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -914,15 +914,6 @@
                 // letterboxed. Hence always let them extend under the cutout.
                 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                 break;
-            case TYPE_NOTIFICATION_SHADE:
-                // If the Keyguard is in a hidden state (occluded by another window), we force to
-                // remove the wallpaper and keyguard flag so that any change in-flight after setting
-                // the keyguard as occluded wouldn't set these flags again.
-                // See {@link #processKeyguardSetHiddenResultLw}.
-                if (mService.mPolicy.isKeyguardOccluded()) {
-                    attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-                }
-                break;
 
             case TYPE_TOAST:
                 // While apps should use the dedicated toast APIs to add such windows
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 16e77f0..7e784ae 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1209,6 +1209,11 @@
                 }
             }
         }
+        // For better split UX, If task launch by the source task which root task is created by
+        // organizer, it should also launch in that root too.
+        if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) {
+            return sourceTask.getRootTask();
+        }
         return null;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2ea0f35..52cf2af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.INPUT_CONSUMER;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
@@ -268,6 +269,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.view.TaskTransitionSpec;
 import android.view.View;
 import android.view.WindowContentFrameStats;
 import android.view.WindowInsets;
@@ -756,6 +758,11 @@
      */
     final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
 
+    /**
+     * Used during task transitions to allow SysUI and launcher to customize task transitions.
+     */
+    TaskTransitionSpec mTaskTransitionSpec;
+
     boolean mHardKeyboardAvailable;
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
     SettingsObserver mSettingsObserver;
@@ -2572,13 +2579,17 @@
             // an exit.
             win.mAnimatingExit = true;
         } else if (win.mDisplayContent.okToAnimate()
-                && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
-            // If the wallpaper is currently behind this
-            // window, we need to change both of them inside
-            // of a transaction to avoid artifacts.
+                && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+                && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+            // If the wallpaper is currently behind this app window, we need to change both of them
+            // inside of a transaction to avoid artifacts.
+            // For NotificationShade, sysui is in charge of running window animation and it updates
+            // the client view visibility only after both NotificationShade and the wallpaper are
+            // hidden. So we don't need to care about exit animation, but can destroy its surface
+            // immediately.
             win.mAnimatingExit = true;
         } else {
-            boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true;
+            boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
             // We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
             // will later actually destroy the surface if we do not do so here. Normally we leave
             // this to the exit animation.
@@ -3457,8 +3468,6 @@
             // Notify whether the root docked task exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
 
-            mRoot.forAllDisplays(dc -> dc.mAppTransition.setCurrentUser(newUserId));
-
             // If the display is already prepared, update the density.
             // Otherwise, we'll update it when it's prepared.
             if (mDisplayReady) {
@@ -8700,4 +8709,22 @@
     public void setTaskSnapshotEnabled(boolean enabled) {
         mTaskSnapshotController.setTaskSnapshotEnabled(enabled);
     }
+
+    @Override
+    public void setTaskTransitionSpec(TaskTransitionSpec spec) {
+        if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "setTaskTransitionSpec()")) {
+            throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+        }
+
+        mTaskTransitionSpec = spec;
+    }
+
+    @Override
+    public void clearTaskTransitionSpec() {
+        if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "clearTaskTransitionSpec()")) {
+            throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+        }
+
+        mTaskTransitionSpec = null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f77b6f2..47aafc2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -259,7 +259,6 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.utils.CoordinateTransforms;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -1177,14 +1176,13 @@
 
     /**
      * @return {@code true} if the application runs in size compatibility mode or has an app level
-     * scaling override set. This method always returns {@code false} on child window because it
-     * should follow parent's scale.
+     * scaling override set.
      * @see CompatModePackages#getCompatScale
      * @see android.content.res.CompatibilityInfo#supportsScreen
      * @see ActivityRecord#hasSizeCompatBounds()
      */
     boolean hasCompatScale() {
-        return (mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord)) && !mIsChildWindow;
+        return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord);
     }
 
     /**
@@ -4478,22 +4476,6 @@
             h = Math.min(h, ph);
         }
 
-        if (mIsChildWindow) {
-            final WindowState parent = getTopParentWindow();
-            if (parent.hasCompatScale()) {
-                // Scale the containing and display frames because they are in screen coordinates.
-                // The position of frames are already relative to parent so only size is scaled.
-                mTmpRect.set(containingFrame);
-                containingFrame = mTmpRect;
-                CoordinateTransforms.scaleRectSize(containingFrame, parent.mInvGlobalScale);
-                if (fitToDisplay) {
-                    mTmpRect2.set(displayFrame);
-                    displayFrame = mTmpRect2;
-                    CoordinateTransforms.scaleRectSize(displayFrame, parent.mInvGlobalScale);
-                }
-            }
-        }
-
         // Set mFrame
         Gravity.apply(attrs.gravity, w, h, containingFrame,
                 (int) (x + attrs.horizontalMargin * pw),
@@ -5124,19 +5106,6 @@
         }
     }
 
-    /**
-     * Expand the given rectangle by this windows surface insets. This
-     * takes you from the 'window size' to the 'surface size'.
-     * The surface insets are positive in each direction, so we inset by
-     * the inverse.
-     */
-    void expandForSurfaceInsets(Rect r) {
-        r.inset(-mAttrs.surfaceInsets.left,
-                -mAttrs.surfaceInsets.top,
-                -mAttrs.surfaceInsets.right,
-                -mAttrs.surfaceInsets.bottom);
-    }
-
     boolean surfaceInsetsChanging() {
         return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
     }
@@ -5454,6 +5423,10 @@
     }
 
     private void updateScaleIfNeeded() {
+        if (mIsChildWindow) {
+            // Child window follows parent's scale.
+            return;
+        }
         float newHScale = mHScale * mGlobalScale * mWallpaperScale;
         float newVScale = mVScale * mGlobalScale * mWallpaperScale;
         if (mLastHScale != newHScale ||
@@ -5537,15 +5510,18 @@
         // If changed, also adjust getTransformationMatrix
         final WindowContainer parentWindowContainer = getParent();
         if (isChildWindow()) {
-            // TODO: This probably falls apart at some point and we should
-            // actually compute relative coordinates.
-
+            final WindowState parent = getParentWindow();
+            outPoint.offset(-parent.mWindowFrames.mFrame.left, -parent.mWindowFrames.mFrame.top);
+            // Undo the scale of window position because the relative coordinates for child are
+            // based on the scaled parent.
+            if (mInvGlobalScale != 1f) {
+                outPoint.x = (int) (outPoint.x * mInvGlobalScale + 0.5f);
+                outPoint.y = (int) (outPoint.y * mInvGlobalScale + 0.5f);
+            }
             // Since the parent was outset by its surface insets, we need to undo the outsetting
             // with insetting by the same amount.
-            final WindowState parent = getParentWindow();
             transformSurfaceInsetsPosition(mTmpPoint, parent.mAttrs.surfaceInsets);
-            outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
-                    -parent.mWindowFrames.mFrame.top + mTmpPoint.y);
+            outPoint.offset(mTmpPoint.x, mTmpPoint.y);
         } else if (parentWindowContainer != null) {
             final Rect parentBounds = isStartingWindowAssociatedToTask()
                     ? mStartingData.mAssociatedTask.getBounds()
@@ -5564,7 +5540,7 @@
             outPoint.offset(outset, outset);
         }
 
-        // Expand for surface insets. See WindowState.expandForSurfaceInsets.
+        // The surface size is larger than the window if the window has positive surface insets.
         transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
         outPoint.offset(-mTmpPoint.x, -mTmpPoint.y);
 
@@ -5576,7 +5552,9 @@
      * scaled, the insets also need to be scaled for surface position in global coordinate.
      */
     private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) {
-        if (!hasCompatScale()) {
+        // Ignore the scale for child window because its insets have been scaled with the
+        // parent surface.
+        if (mGlobalScale == 1f || mIsChildWindow) {
             outPos.x = surfaceInsets.left;
             outPos.y = surfaceInsets.top;
             return;
@@ -5939,7 +5917,8 @@
 
     @Override
     boolean isSyncFinished() {
-        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE) {
+        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE
+                && !isVisibleRequested()) {
             // Don't wait for GONE windows. However, we don't alter the state in case the window
             // becomes un-gone while the syncset is still active.
             return true;
diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
index 6d8e07a..a2f37a5 100644
--- a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
+++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
@@ -152,10 +152,4 @@
         transform.mapRect(tmp);
         inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom);
     }
-
-    /** Scales the rect without changing its position. */
-    public static void scaleRectSize(Rect inOutRect, float scale) {
-        inOutRect.right = inOutRect.left + (int) (inOutRect.width() * scale + .5f);
-        inOutRect.bottom = inOutRect.top + (int) (inOutRect.height() * scale + .5f);
-    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4a2b51d..f351a8c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -150,7 +150,6 @@
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
-import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -2409,15 +2408,6 @@
             mSystemServiceManager.startService(AuthService.class);
             t.traceEnd();
 
-
-            t.traceBegin("StartBackgroundDexOptService");
-            try {
-                BackgroundDexOptService.schedule(context);
-            } catch (Throwable e) {
-                reportWtf("starting StartBackgroundDexOptService", e);
-            }
-            t.traceEnd();
-
             if (!isWatch) {
                 // We don't run this on watches as there are no plans to use the data logged
                 // on watch devices.
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 68b8469..80f2729 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -171,7 +171,7 @@
             </intent-filter>
         </receiver>
 
-        <service android:name="com.android.server.job.MockPriorityJobService"
+        <service android:name="com.android.server.job.MockBiasJobService"
              android:permission="android.permission.BIND_JOB_SERVICE"/>
 
         <activity android:name="com.android.server.pm.BaseShortcutManagerTest$ShortcutActivity"/>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2a5bb18..b1da890 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,7 +16,10 @@
 
 package com.android.server.accessibility;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -38,7 +41,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.os.IBinder;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -51,6 +53,7 @@
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.WindowMagnificationManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -92,6 +95,7 @@
     @Mock private AccessibilityWindowManager mMockA11yWindowManager;
     @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
     @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+    @Mock private UserManagerInternal mMockUserManagerInternal;
     @Mock private IBinder mMockBinder;
     @Mock private IAccessibilityServiceClient mMockServiceClient;
     @Mock private WindowMagnificationManager mMockWindowMagnificationMgr;
@@ -109,16 +113,20 @@
         MockitoAnnotations.initMocks(this);
         LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.addService(
                 WindowManagerInternal.class, mMockWindowManagerService);
         LocalServices.addService(
                 ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+        LocalServices.addService(
+                UserManagerInternal.class, mMockUserManagerInternal);
 
         when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
                 mMockWindowMagnificationMgr);
         when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
                 mMockA11yController);
         when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false);
+        when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true);
         mA11yms = new AccessibilityManagerService(
             InstrumentationRegistry.getContext(),
             mMockPackageManager,
@@ -131,16 +139,15 @@
         mMockResources = mock(Resources.class);
         when(mMockContext.getResources()).thenReturn(mMockResources);
 
-        final AccessibilityUserState userState = new AccessibilityUserState(
+        mUserState = new AccessibilityUserState(
                 mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms);
-        mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+        mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), mUserState);
     }
 
     private void setupAccessibilityServiceConnection() {
         when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(
                 InstrumentationRegistry.getContext().getSystemService(
                         Context.DISPLAY_SERVICE));
-        mUserState = new AccessibilityUserState(UserHandle.USER_SYSTEM, mMockContext, mA11yms);
 
         when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
         mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
@@ -226,4 +233,27 @@
         assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
                 userState.getMagnificationModeLocked());
     }
+
+    @SmallTest
+    public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
+        mUserState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+        mUserState.setMagnificationCapabilitiesLocked(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(true);
+    }
+
+    @SmallTest
+    public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
+        setupAccessibilityServiceConnection();
+        when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);
+
+        // Invokes client change to trigger onUserStateChanged.
+        mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+        verify(mMockWindowMagnificationMgr).requestConnection(true);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
new file mode 100644
index 0000000..bda7cf6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.job;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+import com.android.server.job.MockBiasJobService.TestEnvironment;
+import com.android.server.job.MockBiasJobService.TestEnvironment.Event;
+
+import java.util.ArrayList;
+
+@TargetApi(24)
+public class BiasSchedulingTest extends AndroidTestCase {
+    /** Environment that notifies of JobScheduler callbacks. */
+    private static final TestEnvironment sTestEnvironment = TestEnvironment.getTestEnvironment();
+    /** Handle for the service which receives the execution callbacks from the JobScheduler. */
+    private static ComponentName sJobServiceComponent;
+    private JobScheduler mJobScheduler;
+
+    // The system overrides the test app bias to be a minimum of FOREGROUND_SERVICE. We can
+    // bypass that override by using a bias of at least bound foreground service.
+    private static final int HIGH_BIAS = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE + 1;
+    private static final int LOW_BIAS = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        sTestEnvironment.setUp();
+        sJobServiceComponent = new ComponentName(getContext(), MockBiasJobService.class);
+        mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        mJobScheduler.cancelAll();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mJobScheduler.cancelAll();
+        super.tearDown();
+    }
+
+    public void testLowerBiasJobPreempted() throws Exception {
+        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+            JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
+                    .setBias(LOW_BIAS)
+                    .setOverrideDeadline(0)
+                    .build();
+            mJobScheduler.schedule(job);
+        }
+        final int higherBiasJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+        JobInfo jobHigher = new JobInfo.Builder(higherBiasJobId, sJobServiceComponent)
+                .setBias(HIGH_BIAS)
+                .setMinimumLatency(2000)
+                .setOverrideDeadline(4000)
+                .build();
+        mJobScheduler.schedule(jobHigher);
+        Thread.sleep(10000);  // Wait for jobHigher to preempt one of the lower bias jobs
+
+        Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherBiasJobId);
+        ArrayList<Event> executedEvents = sTestEnvironment.getExecutedEvents();
+        boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution);
+        boolean wasSomeJobPreempted = false;
+        for (Event event: executedEvents) {
+            if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
+                wasSomeJobPreempted = true;
+                break;
+            }
+        }
+        assertTrue("No job was preempted.", wasSomeJobPreempted);
+        assertTrue("Lower bias jobs were not preempted.", wasJobHigherExecuted);
+    }
+
+    public void testHigherBiasJobNotPreempted() throws Exception {
+        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
+            JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
+                    .setBias(HIGH_BIAS)
+                    .setOverrideDeadline(0)
+                    .build();
+            mJobScheduler.schedule(job);
+        }
+        final int lowerBiasJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
+        JobInfo jobLower = new JobInfo.Builder(lowerBiasJobId, sJobServiceComponent)
+                .setBias(LOW_BIAS)
+                .setMinimumLatency(2000)
+                .setOverrideDeadline(3000)
+                .build();
+        mJobScheduler.schedule(jobLower);
+        Thread.sleep(10000);
+
+        Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerBiasJobId);
+        boolean wasLowerExecuted = sTestEnvironment.getExecutedEvents().contains(jobLowerExecution);
+        assertFalse("Higher bias job was preempted.", wasLowerExecuted);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 8eb3cf3..243f7b4 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -313,10 +313,10 @@
     }
 
     @Test
-    public void testPriorityPersisted() throws Exception {
+    public void testBiasPersisted() throws Exception {
         JobInfo.Builder b = new Builder(92, mComponent)
                 .setOverrideDeadline(5000)
-                .setPriority(42)
+                .setBias(42)
                 .setPersisted(true);
         final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
         mTaskStoreUnderTest.add(js);
@@ -325,7 +325,7 @@
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
-        assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
+        assertEquals("Bias not correctly persisted.", 42, loaded.getBias());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java b/services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
rename to services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
index 87881bf..d324d62 100644
--- a/services/tests/servicestests/src/com/android/server/job/MockPriorityJobService.java
+++ b/services/tests/servicestests/src/com/android/server/job/MockBiasJobService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 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.
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.job;
@@ -24,8 +24,8 @@
 import java.util.ArrayList;
 
 @TargetApi(24)
-public class MockPriorityJobService extends JobService {
-    private static final String TAG = "MockPriorityJobService";
+public class MockBiasJobService extends JobService {
+    private static final String TAG = "MockBiasJobService";
 
     @Override
     public void onCreate() {
@@ -36,7 +36,7 @@
     @Override
     public boolean onStartJob(JobParameters params) {
         Log.i(TAG, "Test job executing: " + params.getJobId());
-        TestEnvironment.getTestEnvironment().executedEvents.add(
+        TestEnvironment.getTestEnvironment().mExecutedEvents.add(
                 new TestEnvironment.Event(TestEnvironment.EVENT_START_JOB, params.getJobId()));
         return true;  // Job not finished
     }
@@ -51,7 +51,7 @@
             event = TestEnvironment.EVENT_PREEMPT_JOB;
             Log.d(TAG, "preempted " + String.valueOf(params.getJobId()));
         }
-        TestEnvironment.getTestEnvironment().executedEvents
+        TestEnvironment.getTestEnvironment().mExecutedEvents
                 .add(new TestEnvironment.Event(event, params.getJobId()));
         return false;  // Do not reschedule
     }
@@ -62,15 +62,15 @@
         public static final int EVENT_PREEMPT_JOB = 1;
         public static final int EVENT_STOP_JOB = 2;
 
-        private static TestEnvironment kTestEnvironment;
+        private static TestEnvironment sTestEnvironment;
 
-        private ArrayList<Event> executedEvents = new ArrayList<Event>();
+        private final ArrayList<Event> mExecutedEvents = new ArrayList<>();
 
         public static TestEnvironment getTestEnvironment() {
-            if (kTestEnvironment == null) {
-                kTestEnvironment = new TestEnvironment();
+            if (sTestEnvironment == null) {
+                sTestEnvironment = new TestEnvironment();
             }
-            return kTestEnvironment;
+            return sTestEnvironment;
         }
 
         public static class Event {
@@ -96,11 +96,11 @@
         }
 
         public void setUp() {
-            executedEvents.clear();
+            mExecutedEvents.clear();
         }
 
         public ArrayList<Event> getExecutedEvents() {
-            return executedEvents;
+            return mExecutedEvents;
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
deleted file mode 100644
index 9ecba59..0000000
--- a/services/tests/servicestests/src/com/android/server/job/PrioritySchedulingTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 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.job;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.test.AndroidTestCase;
-
-import com.android.server.job.MockPriorityJobService.TestEnvironment;
-import com.android.server.job.MockPriorityJobService.TestEnvironment.Event;
-
-import java.util.ArrayList;
-
-@TargetApi(24)
-public class PrioritySchedulingTest extends AndroidTestCase {
-    /** Environment that notifies of JobScheduler callbacks. */
-    static TestEnvironment kTestEnvironment = TestEnvironment.getTestEnvironment();
-    /** Handle for the service which receives the execution callbacks from the JobScheduler. */
-    static ComponentName kJobServiceComponent;
-    JobScheduler mJobScheduler;
-
-    // The system overrides the test app priority to be a minimum of FOREGROUND_SERVICE. We can
-    // bypass that override by using a priority of at least bound foreground service.
-    private static final int HIGH_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE + 1;
-    private static final int LOW_PRIORITY = JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        kTestEnvironment.setUp();
-        kJobServiceComponent = new ComponentName(getContext(), MockPriorityJobService.class);
-        mJobScheduler = (JobScheduler) getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        mJobScheduler.cancelAll();
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mJobScheduler.cancelAll();
-        super.tearDown();
-    }
-
-    public void testLowerPriorityJobPreempted() throws Exception {
-        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
-            JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
-                    .setPriority(LOW_PRIORITY)
-                    .setOverrideDeadline(0)
-                    .build();
-            mJobScheduler.schedule(job);
-        }
-        final int higherPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-        JobInfo jobHigher = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
-                .setPriority(HIGH_PRIORITY)
-                .setMinimumLatency(2000)
-                .setOverrideDeadline(4000)
-                .build();
-        mJobScheduler.schedule(jobHigher);
-        Thread.sleep(10000);  // Wait for jobHigher to preempt one of the lower priority jobs
-
-        Event jobHigherExecution = new Event(TestEnvironment.EVENT_START_JOB, higherPriorityJobId);
-        ArrayList<Event> executedEvents = kTestEnvironment.getExecutedEvents();
-        boolean wasJobHigherExecuted = executedEvents.contains(jobHigherExecution);
-        boolean wasSomeJobPreempted = false;
-        for (Event event: executedEvents) {
-            if (event.event == TestEnvironment.EVENT_PREEMPT_JOB) {
-                wasSomeJobPreempted = true;
-                break;
-            }
-        }
-        assertTrue("No job was preempted.", wasSomeJobPreempted);
-        assertTrue("Lower priority jobs were not preempted.", wasJobHigherExecuted);
-    }
-
-    public void testHigherPriorityJobNotPreempted() throws Exception {
-        for (int i = 0; i < JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; ++i) {
-            JobInfo job = new JobInfo.Builder(100 + i, kJobServiceComponent)
-                    .setPriority(HIGH_PRIORITY)
-                    .setOverrideDeadline(0)
-                    .build();
-            mJobScheduler.schedule(job);
-        }
-        final int lowerPriorityJobId = 100 + JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-        JobInfo jobLower = new JobInfo.Builder(lowerPriorityJobId, kJobServiceComponent)
-                .setPriority(LOW_PRIORITY)
-                .setMinimumLatency(2000)
-                .setOverrideDeadline(3000)
-                .build();
-        mJobScheduler.schedule(jobLower);
-        Thread.sleep(10000);
-
-        Event jobLowerExecution = new Event(TestEnvironment.EVENT_START_JOB, lowerPriorityJobId);
-        boolean wasLowerExecuted = kTestEnvironment.getExecutedEvents().contains(jobLowerExecution);
-        assertFalse("Higher priority job was preempted.", wasLowerExecuted);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 82bf2f4..bb0ee02 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -89,6 +89,8 @@
     PackageManagerServiceInjector mMockInjector;
     @Mock
     PackageManagerService mMockPackageManager;
+    @Mock
+    Installer mMockInstaller;
 
     @Before
     public void setupInjector() {
@@ -103,6 +105,7 @@
 
         when(mMockInjector.getDomainVerificationManagerInternal())
                 .thenReturn(domainVerificationManager);
+        when(mMockInjector.getInstaller()).thenReturn(mMockInstaller);
     }
 
     @Before
@@ -434,7 +437,8 @@
         final ParsingPackage basicPackage = createBasicPackage(DUMMY_PACKAGE_NAME)
                 .addUsesPermission(new ParsedUsesPermission(Manifest.permission.FACTORY_TEST, 0));
 
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mMockPackageManager);
+        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
+                mMockPackageManager, mMockInjector);
         final ScanResult scanResult = scanPackageHelper.scanPackageOnlyLI(
                 createBasicScanRequestBuilder(basicPackage).build(),
                 mMockInjector,
@@ -483,7 +487,8 @@
 
     private ScanResult executeScan(
             ScanRequest scanRequest) throws PackageManagerException {
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(mMockPackageManager);
+        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
+                mMockPackageManager, mMockInjector);
         ScanResult result = scanPackageHelper.scanPackageOnlyLI(
                 scanRequest,
                 mMockInjector,
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index aaa74dd..36e988f 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -17,6 +17,7 @@
 package com.android.server.power;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -457,4 +458,33 @@
         watcher.mSevereThresholds.erase();
         assertTrue(Float.isNaN(watcher.getForecast(0)));
     }
+
+    @Test
+    public void testTemperatureWatcherGetForecastUpdate() throws Exception {
+        ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
+
+        // Reduce the inactivity threshold to speed up testing
+        watcher.mInactivityThresholdMillis = 2000;
+
+        // Make sure mSamples is empty before updateTemperature
+        assertTrue(isWatcherSamplesEmpty(watcher));
+
+        // Call getForecast once to trigger updateTemperature
+        watcher.getForecast(0);
+
+        // After 1 second, the samples should be updated
+        Thread.sleep(1000);
+        assertFalse(isWatcherSamplesEmpty(watcher));
+
+        // After mInactivityThresholdMillis, the samples should be cleared
+        Thread.sleep(watcher.mInactivityThresholdMillis);
+        assertTrue(isWatcherSamplesEmpty(watcher));
+    }
+
+    // Helper function to hold mSamples lock, avoid GuardedBy lint errors
+    private boolean isWatcherSamplesEmpty(ThermalManagerService.TemperatureWatcher watcher) {
+        synchronized (watcher.mSamples) {
+            return watcher.mSamples.isEmpty();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 3f8cf9c..16cfd13 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -717,7 +717,8 @@
             hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+                    false);
         }
 
         {
@@ -732,7 +733,7 @@
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
             TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
-                    RecognitionStatus.SUCCESS);
+                    RecognitionStatus.SUCCESS, false);
         }
         verifyNoMoreInteractions(canonicalCallback);
         clearInvocations(canonicalCallback);
@@ -752,7 +753,22 @@
                     99);
             mCanonical.flushCallbacks();
             verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
-            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED);
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+                    false);
+        }
+
+        {
+            final int handle = 87;
+            final int status = 3; // FORCED;
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+
+            hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
+                    99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED,
+                    true);
         }
 
         {
@@ -767,7 +783,21 @@
             mCanonical.flushCallbacks();
             verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
             TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
-                    RecognitionStatus.SUCCESS);
+                    RecognitionStatus.SUCCESS, false);
+        }
+
+        {
+            final int handle = 102;
+            final int status = 3; // FORCED;
+            ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    PhraseRecognitionEvent.class);
+
+            hwCallback.phraseRecognitionCallback_2_1(
+                    TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
+            mCanonical.flushCallbacks();
+            verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
+            TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+                    RecognitionStatus.FORCED, true);
         }
         verifyNoMoreInteractions(canonicalCallback);
         clearInvocations(canonicalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 1daf831..0eba6a3 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.doThrow;
 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;
 
@@ -286,16 +287,31 @@
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
 
-        // Signal a capture from the driver.
-        RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                RecognitionStatus.SUCCESS);
+        {
+            // Signal a capture from the driver (with "still active").
+            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, true);
 
-        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
-                RecognitionEvent.class);
-        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+            verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
 
-        // Validate the event.
-        assertEquals(event, eventCaptor.getValue());
+            // Validate the event.
+            assertEquals(event, eventCaptor.getValue());
+        }
+
+        {
+            // Signal a capture from the driver (without "still active").
+            RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
+                    RecognitionStatus.SUCCESS, false);
+
+            ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                    RecognitionEvent.class);
+            verify(callback, times(2)).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
+
+            // Validate the event.
+            assertEquals(event, eventCaptor.getValue());
+        }
 
         // Unload the model.
         unloadModel(module, handle, hwHandle);
@@ -318,7 +334,7 @@
 
         // Signal a capture from the driver.
         PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.SUCCESS);
+                RecognitionStatus.SUCCESS, false);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
@@ -352,7 +368,7 @@
 
         // Signal a capture from the driver.
         RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED);
+                RecognitionStatus.FORCED, true);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
@@ -420,7 +436,7 @@
 
         // Signal a capture from the driver.
         PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
-                RecognitionStatus.FORCED);
+                RecognitionStatus.FORCED, true);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
@@ -484,7 +500,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
+        hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
@@ -514,7 +530,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED);
+        hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
@@ -604,15 +620,18 @@
             mCallback = callback;
         }
 
-        private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status) {
-            RecognitionEvent event = TestUtil.createRecognitionEvent(status);
+        private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status,
+                boolean recognitionStillActive) {
+            RecognitionEvent event = TestUtil.createRecognitionEvent(status,
+                    recognitionStillActive);
             mCallback.recognitionCallback(hwHandle, event);
             return event;
         }
 
         private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
-                @RecognitionStatus int status) {
-            PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status);
+                @RecognitionStatus int status, boolean recognitionStillActive) {
+            PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status,
+                    recognitionStillActive);
             mCallback.phraseRecognitionCallback(hwHandle, event);
             return event;
         }
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 43d646a..e687a2a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -330,7 +330,8 @@
         return format;
     }
 
-    static RecognitionEvent createRecognitionEvent(@RecognitionStatus int status) {
+    static RecognitionEvent createRecognitionEvent(@RecognitionStatus int status,
+            boolean recognitionStillActive) {
         RecognitionEvent event = new RecognitionEvent();
         event.status = status;
         event.type = SoundModelType.GENERIC;
@@ -346,6 +347,7 @@
         event.audioConfig.base.format = createAudioFormatMp3();
         //event.audioConfig.offloadInfo is irrelevant.
         event.data = new byte[]{31, 32, 33};
+        event.recognitionStillActive = recognitionStillActive;
         return event;
     }
 
@@ -360,7 +362,8 @@
         return halEvent;
     }
 
-    static void validateRecognitionEvent(RecognitionEvent event, @RecognitionStatus int status) {
+    static void validateRecognitionEvent(RecognitionEvent event, @RecognitionStatus int status,
+            boolean recognitionStillActive) {
         assertEquals(status, event.status);
         assertEquals(SoundModelType.GENERIC, event.type);
         assertTrue(event.captureAvailable);
@@ -372,11 +375,13 @@
                 event.audioConfig.base.channelMask);
         assertEquals(createAudioFormatMp3(), event.audioConfig.base.format);
         assertArrayEquals(new byte[]{31, 32, 33}, event.data);
+        assertEquals(recognitionStillActive, event.recognitionStillActive);
     }
 
-    static PhraseRecognitionEvent createPhraseRecognitionEvent(@RecognitionStatus int status) {
+    static PhraseRecognitionEvent createPhraseRecognitionEvent(@RecognitionStatus int status,
+            boolean recognitionStillActive) {
         PhraseRecognitionEvent event = new PhraseRecognitionEvent();
-        event.common = createRecognitionEvent(status);
+        event.common = createRecognitionEvent(status, recognitionStillActive);
 
         PhraseRecognitionExtra extra = new PhraseRecognitionExtra();
         extra.id = 123;
@@ -434,8 +439,8 @@
     }
 
     static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event,
-            @RecognitionStatus int status) {
-        validateRecognitionEvent(event.common, status);
+            @RecognitionStatus int status, boolean recognitionStillActive) {
+        validateRecognitionEvent(event.common, status, recognitionStillActive);
 
         assertEquals(1, event.phraseExtras.length);
         assertEquals(123, event.phraseExtras[0].id);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 577e36c..a834e2b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -118,7 +118,7 @@
             assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
             assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
             assertEquals(canBubble(i), ranking.canBubble());
-            assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
+            assertEquals(isTextChanged(i), ranking.isTextChanged());
             assertEquals(isConversation(i), ranking.isConversation());
             assertEquals(getShortcutInfo(i).getId(), ranking.getConversationShortcutInfo().getId());
             assertEquals(getRankingAdjustment(i), ranking.getRankingAdjustment());
@@ -189,7 +189,7 @@
                 (ArrayList) tweak.getSmartActions(),
                 (ArrayList) tweak.getSmartReplies(),
                 tweak.canBubble(),
-                tweak.visuallyInterruptive(),
+                tweak.isTextChanged(),
                 tweak.isConversation(),
                 tweak.getConversationShortcutInfo(),
                 tweak.getRankingAdjustment(),
@@ -270,7 +270,7 @@
                     getSmartActions(key, i),
                     getSmartReplies(key, i),
                     canBubble(i),
-                    visuallyInterruptive(i),
+                    isTextChanged(i),
                     isConversation(i),
                     getShortcutInfo(i),
                     getRankingAdjustment(i),
@@ -379,7 +379,7 @@
         return index % 4 == 0;
     }
 
-    private boolean visuallyInterruptive(int index) {
+    private boolean isTextChanged(int index) {
         return index % 4 == 0;
     }
 
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 46b4f87..16ee1e8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -182,6 +184,7 @@
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
@@ -314,6 +317,8 @@
     @Mock
     AppOpsManager mAppOpsManager;
     @Mock
+    IAppOpsService mAppOpsService;
+    @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
     @Mock
@@ -449,7 +454,8 @@
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
                 mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
                 mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
-                mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
+                mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
+                mock(TelephonyManager.class),
                 mAmi, mToastRateLimiter, mPermissionHelper);
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
@@ -2127,6 +2133,11 @@
     public void testUpdateAppNotifyCreatorBlock() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
 
+        // should not trigger a broadcast
+        when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
+        mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+        // should trigger a broadcast
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -2149,6 +2160,11 @@
     public void testUpdateAppNotifyCreatorUnblock() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
 
+        // should not trigger a broadcast
+        when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
+        mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+        // should trigger a broadcast
         mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
@@ -3800,6 +3816,27 @@
     }
 
     @Test
+    public void testVisuallyInterruptive_notSeen() throws Exception {
+        NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(original);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, original.getSbn().getId(),
+                original.getSbn().getTag(), mUid, 0,
+                new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                        .setContentTitle("new title").build(),
+                UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        mService.addEnqueuedNotification(update);
+
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(update.getKey());
+        runnable.run();
+        waitForIdle();
+
+        assertFalse(update.isInterruptive());
+    }
+
+    @Test
     public void testApplyAdjustmentMultiUser() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index 01764ce..36d6945 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.notification;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -28,10 +31,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.fail;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -42,6 +48,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -54,6 +61,7 @@
 import android.app.IUriGrantsManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.StatsManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.usage.UsageStatsManagerInternal;
@@ -95,6 +103,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
 import com.android.server.DeviceIdleInternal;
@@ -349,8 +358,8 @@
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
                 mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
                 mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
-                mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
-                mAmi, mToastRateLimiter, mPermissionHelper);
+                mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
+                mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper);
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -568,7 +577,39 @@
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
         mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
 
-        verify(mPermissionHelper, never()).setNotificationPermission(
+        verify(mPermissionHelper).setNotificationPermission(
                 mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+
+        verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    public void testUpdateAppNotifyCreatorBlock() throws Exception {
+        when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_IGNORED);
+
+        mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
+    }
+
+    @Test
+    public void testUpdateAppNotifyCreatorUnblock() throws Exception {
+        when(mAppOpsManager.checkOpNoThrow(anyInt(), eq(mUid), eq(PKG))).thenReturn(MODE_ALLOWED);
+
+        mService.mAppOpsCallback.opChanged(0, mUid, PKG);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 66d1577..ea5de0c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2338,26 +2338,6 @@
     }
 
     @Test
-    public void testGetBlockedAppCount_noApps() {
-        assertEquals(0, mHelper.getBlockedAppCount(0));
-    }
-
-    @Test
-    public void testGetBlockedAppCount_noAppsForUserId() {
-        mHelper.setEnabled(PKG_N_MR1, 100, false);
-        assertEquals(0, mHelper.getBlockedAppCount(9));
-    }
-
-    @Test
-    public void testGetBlockedAppCount_appsForUserId() {
-        mHelper.setEnabled(PKG_N_MR1, 1020, false);
-        mHelper.setEnabled(PKG_N_MR1, 1030, false);
-        mHelper.setEnabled(PKG_N_MR1, 1060, false);
-        mHelper.setEnabled(PKG_N_MR1, 1000, true);
-        assertEquals(3, mHelper.getBlockedAppCount(0));
-    }
-
-    @Test
     public void testAppBlockedLogging() {
         mHelper.setEnabled(PKG_N_MR1, 1020, false);
         assertEquals(1, mLogger.getCalls().size());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 94858d8..8edd111 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -62,6 +62,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
 import com.android.server.LocalServices;
@@ -160,7 +161,8 @@
                     mock(UsageStatsManagerInternal.class),
                     mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
                     mock(UriGrantsManagerInternal.class),
-                    mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
+                    mock(AppOpsManager.class), mock(IAppOpsService.class),
+                    mUm, mock(NotificationHistoryManager.class),
                     mock(StatsManager.class), mock(TelephonyManager.class),
                     mock(ActivityManagerInternal.class),
                     mock(MultiRateLimiter.class), mock(PermissionHelper.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a8a9188..ca2b4ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -78,6 +78,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -563,7 +564,7 @@
         final WindowState child = createWindow(w, TYPE_APPLICATION_PANEL, "child");
 
         assertTrue(w.hasCompatScale());
-        assertFalse(child.hasCompatScale());
+        assertTrue(child.hasCompatScale());
 
         makeWindowVisible(w, child);
         w.setRequestedSize(100, 200);
@@ -574,21 +575,26 @@
         w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
         child.mAttrs.gravity = Gravity.CENTER;
         DisplayContentTests.performLayout(mDisplayContent);
+        final Rect parentFrame = w.getFrame();
+        final Rect childFrame = child.getFrame();
 
         // Frame on screen = 200x400 (200, 200 - 400, 600). Compat frame on client = 100x200.
         final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
         unscaledCompatFrame.scale(overrideScale);
-        final Rect parentFrame = w.getFrame();
-        assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+        assertEquals(parentFrame, unscaledCompatFrame);
 
-        final Rect childFrame = child.getFrame();
-        assertEquals(childFrame, child.getWindowFrames().mCompatFrame);
-        // Child frame = 50x100 (225, 250 - 275, 350) according to Gravity.CENTER.
-        final int childX = parentFrame.left + child.mRequestedWidth / 2;
-        final int childY = parentFrame.top + child.mRequestedHeight / 2;
-        final Rect expectedChildFrame = new Rect(childX, childY, childX + child.mRequestedWidth,
-                childY + child.mRequestedHeight);
-        assertEquals(expectedChildFrame, childFrame);
+        // Frame on screen = 100x200 (250, 300 - 350, 500). Compat frame on client = 50x100.
+        unscaledCompatFrame.set(child.getWindowFrames().mCompatFrame);
+        unscaledCompatFrame.scale(overrideScale);
+        assertEquals(childFrame, unscaledCompatFrame);
+
+        // The position of child is relative to parent. So the local coordinates should be scaled.
+        final Point expectedChildPos = new Point(
+                (int) ((childFrame.left - parentFrame.left) / overrideScale),
+                (int) ((childFrame.top - parentFrame.top) / overrideScale));
+        final Point childPos = new Point();
+        child.transformFrameToSurfacePosition(childFrame.left, childFrame.top, childPos);
+        assertEquals(expectedChildPos, childPos);
 
         // Surface should apply the scale.
         w.prepareSurfaces();
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index be37a91..24ce7e7 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -830,7 +830,7 @@
             return;
         }
 
-        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+        if (!event.recognitionStillActive) {
             model.setStopped();
         }
 
@@ -971,7 +971,7 @@
             return;
         }
 
-        if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+        if (!event.recognitionStillActive) {
             modelData.setStopped();
         }
 
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 1a0e526..77046f2 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -21,16 +21,13 @@
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
-import android.app.job.JobService;
 import android.app.job.JobScheduler;
+import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 import android.os.Handler;
-import android.os.Parcel;
+import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -45,18 +42,15 @@
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
-import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
-import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.time.Duration;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * System-server-local proxy into the {@code IIorap} native service.
@@ -347,7 +341,8 @@
         launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
         launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
 
-        BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+        BackgroundDexOptService.getService().addPackagesUpdatedListener(
+                mDexOptPackagesUpdated);
 
 
         mRegisteredListeners = true;
@@ -555,7 +550,6 @@
 
             JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME);
             builder.setPeriodic(JOB_INTERVAL_MS);
-            builder.setPrefetch(true);
 
             builder.setRequiresCharging(true);
             builder.setRequiresDeviceIdle(true);
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index ec12040..5b44dba 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -43,6 +43,10 @@
     @VisibleForTesting
     static final int FPLMN_BYTE_SIZE = 3;
 
+    // ICCID used for tests by some OEMs
+    // TODO(b/159354974): Replace the constant here with UiccPortInfo.ICCID_REDACTED once ready
+    private static final String TEST_ICCID = "FFFFFFFFFFFFFFFFFFFF";
+
     // A table mapping from a number to a hex character for fast encoding hex strings.
     private static final char[] HEX_CHARS = {
             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
@@ -923,6 +927,9 @@
      * Strip all the trailing 'F' characters of a string, e.g., an ICCID.
      */
     public static String stripTrailingFs(String s) {
+        if (TEST_ICCID.equals(s)) {
+            return s;
+        }
         return s == null ? null : s.replaceAll("(?i)f*$", "");
     }
 
diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.bp b/tests/DynamicCodeLoggerIntegrationTests/Android.bp
new file mode 100644
index 0000000..448d46f
--- /dev/null
+++ b/tests/DynamicCodeLoggerIntegrationTests/Android.bp
@@ -0,0 +1,60 @@
+//
+// Copyright 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_test_helper_library {
+    name: "DynamicCodeLoggerTestLibrary",
+    srcs: ["src/com/android/dcl/**/*.java"],
+
+}
+
+cc_library_shared {
+    name: "DynamicCodeLoggerNativeTestLibrary",
+    srcs: ["src/cpp/com_android_dcl_Jni.cpp"],
+    header_libs: ["jni_headers"],
+    sdk_version: "28",
+    stl: "c++_static",
+}
+
+cc_binary {
+    name: "DynamicCodeLoggerNativeExecutable",
+    srcs: ["src/cpp/test_executable.cpp"],
+}
+
+android_test {
+    name: "DynamicCodeLoggerIntegrationTests",
+
+    sdk_version: "current",
+    test_suites: ["device-tests"],
+    certificate: "shared",
+    srcs: ["src/com/android/server/pm/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+
+    compile_multilib: "both",
+    jni_libs: ["DynamicCodeLoggerNativeTestLibrary"],
+
+    java_resources: [
+        ":DynamicCodeLoggerTestLibrary",
+        ":DynamicCodeLoggerNativeExecutable",
+    ],
+}
diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.mk b/tests/DynamicCodeLoggerIntegrationTests/Android.mk
deleted file mode 100644
index dab8304..0000000
--- a/tests/DynamicCodeLoggerIntegrationTests/Android.mk
+++ /dev/null
@@ -1,95 +0,0 @@
-#
-# Copyright 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-
-# Build a tiny library that the test app can dynamically load
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerTestLibrary
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl)
-
-include $(BUILD_JAVA_LIBRARY)
-
-dynamiccodeloggertest_jar := $(LOCAL_BUILT_MODULE)
-
-
-# Also build a native library that the test app can dynamically load
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerNativeTestLibrary
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
-LOCAL_HEADER_LIBRARIES := jni_headers
-LOCAL_SDK_VERSION := 28
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_SHARED_LIBRARY)
-
-# And a standalone native executable that we can exec.
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := DynamicCodeLoggerNativeExecutable
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES := src/cpp/test_executable.cpp
-
-include $(BUILD_EXECUTABLE)
-
-dynamiccodeloggertest_executable := $(LOCAL_BUILT_MODULE)
-
-# Build the test app itself
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := DynamicCodeLoggerIntegrationTests
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := shared
-LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    truth-prebuilt \
-
-# Include both versions of the .so if we have 2 arch
-LOCAL_MULTILIB := both
-LOCAL_JNI_SHARED_LIBRARIES := \
-    DynamicCodeLoggerNativeTestLibrary \
-
-# This gets us the javalib.jar built by DynamicCodeLoggerTestLibrary above as well as the various
-# native binaries.
-LOCAL_JAVA_RESOURCE_FILES := \
-    $(dynamiccodeloggertest_jar) \
-    $(dynamiccodeloggertest_executable) \
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index 883c172..5430dee 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -114,7 +114,8 @@
         // Obtained via "echo -n copied.jar | sha256sum"
         String expectedNameHash =
                 "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C";
-        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
+        String expectedContentHash = copyAndHashResource(
+                "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile);
 
         // Feed the jar to a class loader and make sure it contains what we expect.
         ClassLoader parentClassLoader = sContext.getClass().getClassLoader();
@@ -135,7 +136,8 @@
         File privateCopyFile = privateFile("copied2.jar");
         String expectedNameHash =
                 "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93";
-        String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile);
+        String expectedContentHash = copyAndHashResource(
+                "/DynamicCodeLoggerTestLibrary.jar", privateCopyFile);
 
         // This time make sure an unknown class loader is an ancestor of the class loader we use.
         ClassLoader knownClassLoader = sContext.getClass().getClassLoader();
diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING
new file mode 100644
index 0000000..ad07205
--- /dev/null
+++ b/tests/InputMethodStressTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "InputMethodStressTest"
+    }
+  ]
+}
diff --git a/tests/LockTaskTests/Android.bp b/tests/LockTaskTests/Android.bp
new file mode 100644
index 0000000..dce681e
--- /dev/null
+++ b/tests/LockTaskTests/Android.bp
@@ -0,0 +1,32 @@
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "LockTaskTests",
+
+    privileged: true,
+
+    sdk_version: "current",
+    certificate: "platform",
+
+    srcs: [
+        "src/**/I*.aidl",
+        "src/**/*.java",
+    ],
+
+}
diff --git a/tests/LockTaskTests/Android.mk b/tests/LockTaskTests/Android.mk
deleted file mode 100644
index 5406ee1..0000000
--- a/tests/LockTaskTests/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/priv-app
-
-LOCAL_PACKAGE_NAME := LockTaskTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_CERTIFICATE := platform
-
-LOCAL_SRC_FILES := $(call all-Iaidl-files-under, src) $(call all-java-files-under, src)
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 4512876..ffc8f47 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -58,6 +58,7 @@
 public class StagedRollbackTest {
     private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
             "watchdog_trigger_failure_count";
+    private static final String REBOOTLESS_APEX_NAME = "test.apex.rebootless";
 
     /**
      * Adopts common shell permissions needed for rollback tests.
@@ -242,7 +243,7 @@
 
     @Test
     public void testRollbackRebootlessApex() throws Exception {
-        final String packageName = "test.apex.rebootless";
+        final String packageName = REBOOTLESS_APEX_NAME;
         assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
 
         // install
@@ -268,6 +269,28 @@
     }
 
     @Test
+    public void testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(REBOOTLESS_APEX_NAME)).isEqualTo(1);
+
+        TestApp apex2 = new TestApp("TestRebootlessApexV2", REBOOTLESS_APEX_NAME, 2,
+                /* isApex= */ true, "test.rebootless_apex_v2.apex");
+        Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN)
+                .commit();
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+
+        RollbackUtils.waitForAvailableRollback(TestApp.A);
+        RollbackUtils.waitForAvailableRollback(REBOOTLESS_APEX_NAME);
+    }
+
+    @Test
+    public void testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify() throws Exception {
+        // Check only rebootless apex is rolled back. Other rollbacks should remain unchanged.
+        assertThat(RollbackUtils.getCommittedRollback(REBOOTLESS_APEX_NAME)).isNotNull();
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+    }
+
+    @Test
     public void hasMainlineModule() throws Exception {
         String pkgName = getModuleMetadataPackageName();
         boolean existed =  InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 3576a78..1ab59a8 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -192,6 +192,18 @@
     }
 
     /**
+     * Tests only rebootless apex (if any) is rolled back when native crash happens
+     */
+    @Test
+    public void testNativeWatchdogTriggersRebootlessApexRollback() throws Exception {
+        pushTestApex("test.rebootless_apex_v1.apex");
+        runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install");
+        crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+        getDevice().waitForDeviceAvailable();
+        runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify");
+    }
+
+    /**
      * Tests that packages are monitored across multiple reboots.
      */
     @Test
@@ -253,4 +265,18 @@
             return false;
         }
     }
+
+    private void crashProcess(String processName, int numberOfCrashes) throws Exception {
+        String pid = "";
+        String lastPid = "invalid";
+        for (int i = 0; i < numberOfCrashes; ++i) {
+            // This condition makes sure before we kill the process, the process is running AND
+            // the last crash was finished.
+            while ("".equals(pid) || lastPid.equals(pid)) {
+                pid = getDevice().executeShellCommand("pidof " + processName);
+            }
+            getDevice().executeShellCommand("kill " + pid);
+            lastPid = pid;
+        }
+    }
 }
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 77f0ef0..dae89b0 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -440,8 +440,8 @@
 
       // Move the type into a new package
       auto& other_package = new_packages[index];
-      type_inserter.Insert(other_package.types, std::move(type));
       type_new_package_index[type.type] = index + 1;
+      type_inserter.Insert(other_package.types, std::move(type));
       type_it = package.types.erase(type_it);
     }
   }