Merge "3/ Add wmHelper to controll the state of test activity" into sc-dev
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 ac6eb32..c5d3b7a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -53,6 +53,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.LimitExceededException;
 import android.os.Looper;
@@ -151,6 +152,9 @@
     private static final boolean ENFORCE_MAX_JOBS = true;
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
+    /** The number of the most recently completed jobs to keep track of for debugging purposes. */
+    private static final int NUM_COMPLETED_JOB_HISTORY =
+            Build.IS_USERDEBUG || Build.IS_ENG ? 25 : 0;
 
     @VisibleForTesting
     public static Clock sSystemClock = Clock.systemUTC();
@@ -297,6 +301,10 @@
      */
     boolean mReportedActive;
 
+    private int mLastCompletedJobIndex = 0;
+    private final JobStatus[] mLastCompletedJobs = new JobStatus[NUM_COMPLETED_JOB_HISTORY];
+    private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
+
     /**
      * A mapping of which uids are currently in the foreground to their effective priority.
      */
@@ -1752,6 +1760,10 @@
             Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
         }
 
+        mLastCompletedJobs[mLastCompletedJobIndex] = jobStatus;
+        mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
+        mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;
+
         // Intentionally not checking expedited job quota here. An app can't find out if it's run
         // out of quota when it asks JS to reschedule an expedited job. Instead, the rescheduled
         // EJ will just be demoted to a regular job if the app has no EJ quota left.
@@ -3298,6 +3310,37 @@
                 }
             }
             pw.decreaseIndent();
+
+            pw.println();
+            boolean recentPrinted = false;
+            pw.println("Recently completed jobs:");
+            pw.increaseIndent();
+            for (int r = 1; r <= NUM_COMPLETED_JOB_HISTORY; ++r) {
+                // Print most recent first
+                final int idx = (mLastCompletedJobIndex + NUM_COMPLETED_JOB_HISTORY - r)
+                        % NUM_COMPLETED_JOB_HISTORY;
+                final JobStatus job = mLastCompletedJobs[idx];
+                if (job != null) {
+                    if (!predicate.test(job)) {
+                        continue;
+                    }
+                    recentPrinted = true;
+                    TimeUtils.formatDuration(mLastCompletedJobTimeElapsed[idx], nowElapsed, pw);
+                    pw.println();
+                    // Double indent for readability
+                    pw.increaseIndent();
+                    pw.increaseIndent();
+                    job.dump(pw, true, nowElapsed);
+                    pw.decreaseIndent();
+                    pw.decreaseIndent();
+                }
+            }
+            if (!recentPrinted) {
+                pw.println("None");
+            }
+            pw.decreaseIndent();
+            pw.println();
+
             if (filterUid == -1) {
                 pw.println();
                 pw.print("mReadyToRock="); pw.println(mReadyToRock);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 58396eb..a230b23 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -17,6 +17,7 @@
 package com.android.server.job.controllers;
 
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -59,6 +60,8 @@
 
     private final AppStateTrackerImpl mAppStateTracker;
 
+    private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
+
     public BackgroundJobsController(JobSchedulerService service) {
         super(service);
 
@@ -69,7 +72,7 @@
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        updateSingleJobRestrictionLocked(jobStatus, UNKNOWN);
+        updateSingleJobRestrictionLocked(jobStatus, sElapsedRealtimeClock.millis(), UNKNOWN);
     }
 
     @Override
@@ -79,7 +82,7 @@
 
     @Override
     public void evaluateStateLocked(JobStatus jobStatus) {
-        updateSingleJobRestrictionLocked(jobStatus, UNKNOWN);
+        updateSingleJobRestrictionLocked(jobStatus, sElapsedRealtimeClock.millis(), UNKNOWN);
     }
 
     @Override
@@ -163,33 +166,34 @@
     }
 
     private void updateJobRestrictionsLocked(int filterUid, int newActiveState) {
-        final UpdateJobFunctor updateTrackedJobs = new UpdateJobFunctor(newActiveState);
+        mUpdateJobFunctor.prepare(newActiveState);
 
         final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
 
         final JobStore store = mService.getJobStore();
         if (filterUid > 0) {
-            store.forEachJobForSourceUid(filterUid, updateTrackedJobs);
+            store.forEachJobForSourceUid(filterUid, mUpdateJobFunctor);
         } else {
-            store.forEachJob(updateTrackedJobs);
+            store.forEachJob(mUpdateJobFunctor);
         }
 
         final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
         if (DEBUG) {
             Slog.d(TAG, String.format(
                     "Job status updated: %d/%d checked/total jobs, %d us",
-                    updateTrackedJobs.mCheckedCount,
-                    updateTrackedJobs.mTotalCount,
+                    mUpdateJobFunctor.mCheckedCount,
+                    mUpdateJobFunctor.mTotalCount,
                     (time / 1000)
-                    ));
+            ));
         }
 
-        if (updateTrackedJobs.mChanged) {
+        if (mUpdateJobFunctor.mChanged) {
             mStateChangedListener.onControllerStateChanged();
         }
     }
 
-    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {
+    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, final long nowElapsed,
+            int activeState) {
         final int uid = jobStatus.getSourceUid();
         final String packageName = jobStatus.getSourcePackageName();
 
@@ -205,26 +209,32 @@
         if (isActive && jobStatus.getStandbyBucket() == NEVER_INDEX) {
             jobStatus.maybeLogBucketMismatch();
         }
-        boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
+        boolean didChange =
+                jobStatus.setBackgroundNotRestrictedConstraintSatisfied(nowElapsed, canRun);
         didChange |= jobStatus.setUidActive(isActive);
         return didChange;
     }
 
     private final class UpdateJobFunctor implements Consumer<JobStatus> {
-        final int activeState;
+        int mActiveState;
         boolean mChanged = false;
         int mTotalCount = 0;
         int mCheckedCount = 0;
+        long mUpdateTimeElapsed = 0;
 
-        public UpdateJobFunctor(int newActiveState) {
-            activeState = newActiveState;
+        void prepare(int newActiveState) {
+            mActiveState = newActiveState;
+            mUpdateTimeElapsed = sElapsedRealtimeClock.millis();
+            mChanged = false;
+            mTotalCount = 0;
+            mCheckedCount = 0;
         }
 
         @Override
         public void accept(JobStatus jobStatus) {
             mTotalCount++;
             mCheckedCount++;
-            if (updateSingleJobRestrictionLocked(jobStatus, activeState)) {
+            if (updateSingleJobRestrictionLocked(jobStatus, mUpdateTimeElapsed, mActiveState)) {
                 mChanged = true;
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 28269c8..6fd0948 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -65,10 +65,12 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasPowerConstraint()) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             mTrackedTasks.add(taskStatus);
             taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
-            taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
-            taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
+            taskStatus.setChargingConstraintSatisfied(nowElapsed, mChargeTracker.isOnStablePower());
+            taskStatus.setBatteryNotLowConstraintSatisfied(
+                    nowElapsed, mChargeTracker.isBatteryNotLow());
         }
     }
 
@@ -97,14 +99,15 @@
         if (DEBUG) {
             Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
         }
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         boolean reportChange = false;
         for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
             final JobStatus ts = mTrackedTasks.valueAt(i);
-            boolean previous = ts.setChargingConstraintSatisfied(stablePower);
+            boolean previous = ts.setChargingConstraintSatisfied(nowElapsed, stablePower);
             if (previous != stablePower) {
                 reportChange = true;
             }
-            previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
+            previous = ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow);
             if (previous != batteryNotLow) {
                 reportChange = true;
             }
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 14484ff..6e542f3 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
@@ -20,6 +20,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
@@ -461,11 +462,12 @@
         final Network network = mConnManager.getActiveNetworkForUid(
                 jobStatus.getSourceUid(), jobStatus.shouldIgnoreNetworkBlocking());
         final NetworkCapabilities capabilities = getNetworkCapabilities(network);
-        return updateConstraintsSatisfied(jobStatus, network, capabilities);
+        return updateConstraintsSatisfied(jobStatus, sElapsedRealtimeClock.millis(),
+                network, capabilities);
     }
 
-    private boolean updateConstraintsSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
+    private boolean updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed,
+            Network network, NetworkCapabilities capabilities) {
         // TODO: consider matching against non-active networks
 
         final boolean ignoreBlocked = jobStatus.shouldIgnoreNetworkBlocking();
@@ -476,7 +478,7 @@
         final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
 
         final boolean changed = jobStatus
-                .setConnectivityConstraintSatisfied(connected && satisfied);
+                .setConnectivityConstraintSatisfied(nowElapsed, connected && satisfied);
 
         // Pass along the evaluated network for job to use; prevents race
         // conditions as default routes change over time, and opens the door to
@@ -530,6 +532,7 @@
         NetworkCapabilities exemptedNetworkCapabilities = null;
         boolean exemptedNetworkMatch = false;
 
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         boolean changed = false;
         for (int i = jobs.size() - 1; i >= 0; i--) {
             final JobStatus js = jobs.valueAt(i);
@@ -555,7 +558,7 @@
             // job hasn't yet been evaluated against the currently
             // active network; typically when we just lost a network.
             if (match || !Objects.equals(js.network, net)) {
-                changed |= updateConstraintsSatisfied(js, net, netCap);
+                changed |= updateConstraintsSatisfied(js, nowElapsed, net, netCap);
             }
         }
         return changed;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 131a6d4..8b0da34 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.annotation.UserIdInt;
 import android.app.job.JobInfo;
 import android.database.ContentObserver;
@@ -74,6 +76,7 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasContentTriggerConstraint()) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             if (taskStatus.contentObserverJobInstance == null) {
                 taskStatus.contentObserverJobInstance = new JobInstance(taskStatus);
             }
@@ -110,7 +113,7 @@
             }
             taskStatus.changedAuthorities = null;
             taskStatus.changedUris = null;
-            taskStatus.setContentTriggerConstraintSatisfied(havePendingUris);
+            taskStatus.setContentTriggerConstraintSatisfied(nowElapsed, havePendingUris);
         }
         if (lastJob != null && lastJob.contentObserverJobInstance != null) {
             // And now we can detach the instance state from the last job.
@@ -295,7 +298,8 @@
             boolean reportChange = false;
             synchronized (mLock) {
                 if (mTriggerPending) {
-                    if (mJobStatus.setContentTriggerConstraintSatisfied(true)) {
+                    final long nowElapsed = sElapsedRealtimeClock.millis();
+                    if (mJobStatus.setContentTriggerConstraintSatisfied(nowElapsed, true)) {
                         reportChange = true;
                     }
                     unscheduleLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 04b4164..192f5e6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -104,8 +106,10 @@
                                     + Arrays.toString(mPowerSaveTempWhitelistAppIds));
                         }
                         boolean changed = false;
+                        final long nowElapsed = sElapsedRealtimeClock.millis();
                         for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
-                            changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
+                            changed |=
+                                    updateTaskStateLocked(mAllowInIdleJobs.valueAt(i), nowElapsed);
                         }
                         if (changed) {
                             mStateChangedListener.onControllerStateChanged();
@@ -147,6 +151,7 @@
             }
             mDeviceIdleMode = enabled;
             if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
+            mDeviceIdleUpdateFunctor.prepare();
             if (enabled) {
                 mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
                 mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
@@ -180,7 +185,7 @@
             Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
         }
         mForegroundUids.put(uid, active);
-        mDeviceIdleUpdateFunctor.mChanged = false;
+        mDeviceIdleUpdateFunctor.prepare();
         mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
         if (mDeviceIdleUpdateFunctor.mChanged) {
             mStateChangedListener.onControllerStateChanged();
@@ -203,12 +208,12 @@
                 UserHandle.getAppId(job.getSourceUid()));
     }
 
-    private boolean updateTaskStateLocked(JobStatus task) {
+    private boolean updateTaskStateLocked(JobStatus task, final long nowElapsed) {
         final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
                 && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
         final boolean whitelisted = isWhitelistedLocked(task);
         final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
-        return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
+        return task.setDeviceNotDozingConstraintSatisfied(nowElapsed, enableTask, whitelisted);
     }
 
     @Override
@@ -216,7 +221,7 @@
         if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
             mAllowInIdleJobs.add(jobStatus);
         }
-        updateTaskStateLocked(jobStatus);
+        updateTaskStateLocked(jobStatus, sElapsedRealtimeClock.millis());
     }
 
     @Override
@@ -282,10 +287,16 @@
 
     final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> {
         boolean mChanged;
+        long mUpdateTimeElapsed = 0;
+
+        void prepare() {
+            mChanged = false;
+            mUpdateTimeElapsed = sElapsedRealtimeClock.millis();
+        }
 
         @Override
         public void accept(JobStatus jobStatus) {
-            mChanged |= updateTaskStateLocked(jobStatus);
+            mChanged |= updateTaskStateLocked(jobStatus, mUpdateTimeElapsed);
         }
     }
 
@@ -300,7 +311,7 @@
                 case PROCESS_BACKGROUND_JOBS:
                     // Just process all the jobs, the ones in foreground should already be running.
                     synchronized (mLock) {
-                        mDeviceIdleUpdateFunctor.mChanged = false;
+                        mDeviceIdleUpdateFunctor.prepare();
                         mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
                         if (mDeviceIdleUpdateFunctor.mChanged) {
                             mStateChangedListener.onControllerStateChanged();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index 2fe827e..e26a3c6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
@@ -58,9 +60,10 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasIdleConstraint()) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             mTrackedTasks.add(taskStatus);
             taskStatus.setTrackingController(JobStatus.TRACKING_IDLE);
-            taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle());
+            taskStatus.setIdleConstraintSatisfied(nowElapsed, mIdleTracker.isIdle());
         }
     }
 
@@ -90,8 +93,9 @@
     @Override
     public void reportNewIdleState(boolean isIdle) {
         synchronized (mLock) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
-                mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
+                mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(nowElapsed, isIdle);
             }
         }
         mStateChangedListener.onControllerStateChanged();
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 6917fb5..5bdeb38 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
@@ -73,6 +73,8 @@
     private static final String TAG = "JobScheduler.JobStatus";
     static final boolean DEBUG = JobSchedulerService.DEBUG;
 
+    private static final int NUM_CONSTRAINT_CHANGE_HISTORY = 10;
+
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
@@ -350,6 +352,10 @@
      */
     private Pair<Long, Long> mPersistedUtcTimes;
 
+    private int mConstraintChangeHistoryIndex = 0;
+    private final long[] mConstraintUpdatedTimesElapsed = new long[NUM_CONSTRAINT_CHANGE_HISTORY];
+    private final int[] mConstraintStatusHistory = new int[NUM_CONSTRAINT_CHANGE_HISTORY];
+
     /**
      * For use only by ContentObserverController: state it is maintaining about content URIs
      * being observed.
@@ -1090,28 +1096,28 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setChargingConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
+    boolean setChargingConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_CHARGING, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setBatteryNotLowConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
+    boolean setBatteryNotLowConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setStorageNotLowConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
+    boolean setStorageNotLowConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setTimingDelayConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
+    boolean setTimingDelayConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setDeadlineConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_DEADLINE, state)) {
+    boolean setDeadlineConstraintSatisfied(final long nowElapsed, boolean state) {
+        if (setConstraintSatisfied(CONSTRAINT_DEADLINE, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyDeadlineSatisfied = !job.isPeriodic() && hasDeadlineConstraint() && state;
             return true;
@@ -1120,24 +1126,25 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setIdleConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_IDLE, state);
+    boolean setIdleConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_IDLE, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setConnectivityConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
+    boolean setConnectivityConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setContentTriggerConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
+    boolean setContentTriggerConstraintSatisfied(final long nowElapsed, boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, nowElapsed, state);
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
+    boolean setDeviceNotDozingConstraintSatisfied(final long nowElapsed,
+            boolean state, boolean whitelisted) {
         dozeWhitelisted = whitelisted;
-        if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
+        if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyNotDozing = state || canRunInDoze();
             return true;
@@ -1146,8 +1153,8 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state)) {
+    boolean setBackgroundNotRestrictedConstraintSatisfied(final long nowElapsed, boolean state) {
+        if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyNotRestrictedInBg = state;
             return true;
@@ -1156,8 +1163,8 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setQuotaConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_WITHIN_QUOTA, state)) {
+    boolean setQuotaConstraintSatisfied(final long nowElapsed, boolean state) {
+        if (setConstraintSatisfied(CONSTRAINT_WITHIN_QUOTA, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyWithinQuota = state;
             return true;
@@ -1166,8 +1173,8 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setExpeditedJobQuotaConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_WITHIN_EXPEDITED_QUOTA, state)) {
+    boolean setExpeditedJobQuotaConstraintSatisfied(final long nowElapsed, boolean state) {
+        if (setConstraintSatisfied(CONSTRAINT_WITHIN_EXPEDITED_QUOTA, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyWithinExpeditedQuota = state;
             // DeviceIdleJobsController currently only tracks jobs with the WILL_BE_FOREGROUND flag.
@@ -1190,7 +1197,7 @@
     }
 
     /** @return true if the constraint was changed, false otherwise. */
-    boolean setConstraintSatisfied(int constraint, boolean state) {
+    boolean setConstraintSatisfied(int constraint, final long nowElapsed, boolean state) {
         boolean old = (satisfiedConstraints&constraint) != 0;
         if (old == state) {
             return false;
@@ -1212,6 +1219,12 @@
                             : FrameworkStatsLog
                                     .SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
         }
+
+        mConstraintUpdatedTimesElapsed[mConstraintChangeHistoryIndex] = nowElapsed;
+        mConstraintStatusHistory[mConstraintChangeHistoryIndex] = satisfiedConstraints;
+        mConstraintChangeHistoryIndex =
+                (mConstraintChangeHistoryIndex + 1) % NUM_CONSTRAINT_CHANGE_HISTORY;
+
         return true;
     }
 
@@ -1700,7 +1713,7 @@
     }
 
     // Dumpsys infrastructure
-    public void dump(IndentingPrintWriter pw,  boolean full, long elapsedRealtimeMillis) {
+    public void dump(IndentingPrintWriter pw,  boolean full, long nowElapsed) {
         UserHandle.formatUid(pw, callingUid);
         pw.print(" tag="); pw.println(tag);
 
@@ -1830,6 +1843,22 @@
             dumpConstraints(pw,
                     ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
             pw.println();
+
+            pw.println("Constraint history:");
+            pw.increaseIndent();
+            for (int h = 0; h < NUM_CONSTRAINT_CHANGE_HISTORY; ++h) {
+                final int idx = (h + mConstraintChangeHistoryIndex) % NUM_CONSTRAINT_CHANGE_HISTORY;
+                if (mConstraintUpdatedTimesElapsed[idx] == 0) {
+                    continue;
+                }
+                TimeUtils.formatDuration(mConstraintUpdatedTimesElapsed[idx], nowElapsed, pw);
+                // dumpConstraints prepends with a space, so no need to add a space after the =
+                pw.print(" =");
+                dumpConstraints(pw, mConstraintStatusHistory[idx]);
+                pw.println();
+            }
+            pw.decreaseIndent();
+
             if (dozeWhitelisted) {
                 pw.println("Doze whitelisted: true");
             }
@@ -1910,26 +1939,25 @@
         pw.increaseIndent();
         if (whenStandbyDeferred != 0) {
             pw.print("Deferred since: ");
-            TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw);
+            TimeUtils.formatDuration(whenStandbyDeferred, nowElapsed, pw);
             pw.println();
         }
         if (mFirstForceBatchedTimeElapsed != 0) {
             pw.print("Time since first force batch attempt: ");
-            TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw);
+            TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, nowElapsed, pw);
             pw.println();
         }
         pw.decreaseIndent();
 
         pw.print("Enqueue time: ");
-        TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
+        TimeUtils.formatDuration(enqueueTime, nowElapsed, pw);
         pw.println();
         pw.print("Run time: earliest=");
-        formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis);
+        formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, nowElapsed);
         pw.print(", latest=");
-        formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis);
+        formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, nowElapsed);
         pw.print(", original latest=");
-        formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis,
-                NO_LATEST_RUNTIME, elapsedRealtimeMillis);
+        formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis, NO_LATEST_RUNTIME, nowElapsed);
         pw.println();
         if (numFailures != 0) {
             pw.print("Num failures: "); pw.println(numFailures);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 2196b16..824fa7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -626,6 +626,7 @@
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
@@ -636,11 +637,11 @@
         jobs.add(jobStatus);
         jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
         final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
-        setConstraintSatisfied(jobStatus, isWithinQuota);
+        setConstraintSatisfied(jobStatus, nowElapsed, isWithinQuota);
         final boolean outOfEJQuota;
         if (jobStatus.isRequestedExpeditedJob()) {
             final boolean isWithinEJQuota = isWithinEJQuotaLocked(jobStatus);
-            setExpeditedConstraintSatisfied(jobStatus, isWithinEJQuota);
+            setExpeditedConstraintSatisfied(jobStatus, nowElapsed, isWithinEJQuota);
             outOfEJQuota = !isWithinEJQuota;
         } else {
             outOfEJQuota = false;
@@ -1397,7 +1398,8 @@
         synchronized (mLock) {
             final ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
             quota.transactOnDebitsLocked(-credit);
-            if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
+            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                    userId, packageName)) {
                 mStateChangedListener.onControllerStateChanged();
             }
         }
@@ -1499,11 +1501,12 @@
 
     private void maybeUpdateAllConstraintsLocked() {
         boolean changed = false;
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         for (int u = 0; u < mTrackedJobs.numMaps(); ++u) {
             final int userId = mTrackedJobs.keyAt(u);
             for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
                 final String packageName = mTrackedJobs.keyAt(u, p);
-                changed |= maybeUpdateConstraintForPkgLocked(userId, packageName);
+                changed |= maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName);
             }
         }
         if (changed) {
@@ -1516,7 +1519,7 @@
      *
      * @return true if at least one job had its bit changed
      */
-    private boolean maybeUpdateConstraintForPkgLocked(final int userId,
+    private boolean maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId,
             @NonNull final String packageName) {
         ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
         if (jobs == null || jobs.size() == 0) {
@@ -1533,21 +1536,21 @@
             if (isTopStartedJobLocked(js)) {
                 // Job was started while the app was in the TOP state so we should allow it to
                 // finish.
-                changed |= js.setQuotaConstraintSatisfied(true);
+                changed |= js.setQuotaConstraintSatisfied(nowElapsed, true);
             } else if (realStandbyBucket != ACTIVE_INDEX
                     && realStandbyBucket == js.getEffectiveStandbyBucket()) {
                 // An app in the ACTIVE bucket may be out of quota while the job could be in quota
                 // for some reason. Therefore, avoid setting the real value here and check each job
                 // individually.
-                changed |= setConstraintSatisfied(js, realInQuota);
+                changed |= setConstraintSatisfied(js, nowElapsed, realInQuota);
             } else {
                 // This job is somehow exempted. Need to determine its own quota status.
-                changed |= setConstraintSatisfied(js, isWithinQuotaLocked(js));
+                changed |= setConstraintSatisfied(js, nowElapsed, isWithinQuotaLocked(js));
             }
 
             if (js.isRequestedExpeditedJob()) {
                 boolean isWithinEJQuota = isWithinEJQuotaLocked(js);
-                changed |= setExpeditedConstraintSatisfied(js, isWithinEJQuota);
+                changed |= setExpeditedConstraintSatisfied(js, nowElapsed, isWithinEJQuota);
                 outOfEJQuota |= !isWithinEJQuota;
             }
         }
@@ -1566,14 +1569,21 @@
         private final SparseArrayMap<String, Integer> mToScheduleStartAlarms =
                 new SparseArrayMap<>();
         public boolean wasJobChanged;
+        long mUpdateTimeElapsed = 0;
+
+        void prepare() {
+            mUpdateTimeElapsed = sElapsedRealtimeClock.millis();
+        }
 
         @Override
         public void accept(JobStatus jobStatus) {
-            wasJobChanged |= setConstraintSatisfied(jobStatus, isWithinQuotaLocked(jobStatus));
+            wasJobChanged |= setConstraintSatisfied(
+                    jobStatus, mUpdateTimeElapsed, isWithinQuotaLocked(jobStatus));
             final boolean outOfEJQuota;
             if (jobStatus.isRequestedExpeditedJob()) {
                 final boolean isWithinEJQuota = isWithinEJQuotaLocked(jobStatus);
-                wasJobChanged |= setExpeditedConstraintSatisfied(jobStatus, isWithinEJQuota);
+                wasJobChanged |= setExpeditedConstraintSatisfied(
+                        jobStatus, mUpdateTimeElapsed, isWithinEJQuota);
                 outOfEJQuota = !isWithinEJQuota;
             } else {
                 outOfEJQuota = false;
@@ -1611,6 +1621,7 @@
     private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
 
     private boolean maybeUpdateConstraintForUidLocked(final int uid) {
+        mUpdateUidConstraints.prepare();
         mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
 
         mUpdateUidConstraints.postProcess();
@@ -1716,21 +1727,22 @@
         mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
     }
 
-    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
+    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
+            boolean isWithinQuota) {
         if (!isWithinQuota && jobStatus.getWhenStandbyDeferred() == 0) {
             // Mark that the job is being deferred due to buckets.
-            jobStatus.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
+            jobStatus.setWhenStandbyDeferred(nowElapsed);
         }
-        return jobStatus.setQuotaConstraintSatisfied(isWithinQuota);
+        return jobStatus.setQuotaConstraintSatisfied(nowElapsed, isWithinQuota);
     }
 
     /**
      * If the satisfaction changes, this will tell connectivity & background jobs controller to
      * also re-evaluate their state.
      */
-    private boolean setExpeditedConstraintSatisfied(@NonNull JobStatus jobStatus,
+    private boolean setExpeditedConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
             boolean isWithinQuota) {
-        if (jobStatus.setExpeditedJobQuotaConstraintSatisfied(isWithinQuota)) {
+        if (jobStatus.setExpeditedJobQuotaConstraintSatisfied(nowElapsed, isWithinQuota)) {
             mBackgroundJobsController.evaluateStateLocked(jobStatus);
             mConnectivityController.evaluateStateLocked(jobStatus);
             if (isWithinQuota && jobStatus.isReady()) {
@@ -2188,7 +2200,8 @@
                         final ShrinkableDebits quota =
                                 getEJQuotaLocked(mPkg.userId, mPkg.packageName);
                         quota.transactOnDebitsLocked(-mEJRewardTopAppMs * numTimeChunks);
-                        if (maybeUpdateConstraintForPkgLocked(mPkg.userId, mPkg.packageName)) {
+                        if (maybeUpdateConstraintForPkgLocked(nowElapsed,
+                                mPkg.userId, mPkg.packageName)) {
                             mStateChangedListener.onControllerStateChanged();
                         }
                     }
@@ -2292,7 +2305,8 @@
             if (timer != null && timer.isActive()) {
                 timer.rescheduleCutoff();
             }
-            if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
+            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                    userId, packageName)) {
                 mStateChangedListener.onControllerStateChanged();
             }
         }
@@ -2417,7 +2431,8 @@
                         if (timeRemainingMs <= 50) {
                             // Less than 50 milliseconds left. Start process of shutting down jobs.
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
-                            if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) {
+                            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                                    pkg.userId, pkg.packageName)) {
                                 mStateChangedListener.onControllerStateChanged();
                             }
                         } else {
@@ -2444,7 +2459,8 @@
                                 pkg.userId, pkg.packageName);
                         if (timeRemainingMs <= 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
-                            if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) {
+                            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                                    pkg.userId, pkg.packageName)) {
                                 mStateChangedListener.onControllerStateChanged();
                             }
                         } else {
@@ -2475,7 +2491,8 @@
                         if (DEBUG) {
                             Slog.d(TAG, "Checking pkg " + string(userId, packageName));
                         }
-                        if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
+                        if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                                userId, packageName)) {
                             mStateChangedListener.onControllerStateChanged();
                         }
                         break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
index 0731918..8678913 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
@@ -61,9 +61,11 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasStorageNotLowConstraint()) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             mTrackedTasks.add(taskStatus);
             taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
-            taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
+            taskStatus.setStorageNotLowConstraintSatisfied(
+                    nowElapsed, mStorageTracker.isStorageNotLow());
         }
     }
 
@@ -76,12 +78,13 @@
     }
 
     private void maybeReportNewStorageState() {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         final boolean storageNotLow = mStorageTracker.isStorageNotLow();
         boolean reportChange = false;
         synchronized (mLock) {
             for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
                 final JobStatus ts = mTrackedTasks.valueAt(i);
-                reportChange |= ts.setStorageNotLowConstraintSatisfied(storageNotLow);
+                reportChange |= ts.setStorageNotLowConstraintSatisfied(nowElapsed, storageNotLow);
             }
         }
         if (storageNotLow) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index ede14ec..e8ebfb5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -258,9 +258,9 @@
 
         if (jobDeadline <= nowElapsedMillis) {
             if (job.hasTimingDelayConstraint()) {
-                job.setTimingDelayConstraintSatisfied(true);
+                job.setTimingDelayConstraintSatisfied(nowElapsedMillis, true);
             }
-            job.setDeadlineConstraintSatisfied(true);
+            job.setDeadlineConstraintSatisfied(nowElapsedMillis, true);
             return true;
         }
         return false;
@@ -332,7 +332,7 @@
     private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) {
         final long jobDelayTime = job.getEarliestRunTime();
         if (jobDelayTime <= nowElapsedMillis) {
-            job.setTimingDelayConstraintSatisfied(true);
+            job.setTimingDelayConstraintSatisfied(nowElapsedMillis, true);
             return true;
         }
         return false;
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 5c08704..d4da5e5 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -34,7 +34,6 @@
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
 
-#include <ui/DisplayInfo.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 
diff --git a/core/api/current.txt b/core/api/current.txt
index d6c783d..b0375b6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11786,6 +11786,8 @@
     field public int category;
     field public String className;
     field public int compatibleWidthLimitDp;
+    field public int compileSdkVersion;
+    field @Nullable public String compileSdkVersionCodename;
     field public String dataDir;
     field public int descriptionRes;
     field public String deviceProtectedDataDir;
@@ -14615,11 +14617,6 @@
     enum_constant public static final android.graphics.BlurMaskFilter.Blur SOLID;
   }
 
-  public final class BlurShader extends android.graphics.Shader {
-    ctor public BlurShader(float, float, @Nullable android.graphics.Shader);
-    ctor public BlurShader(float, float, @Nullable android.graphics.Shader, @NonNull android.graphics.Shader.TileMode);
-  }
-
   public class Camera {
     ctor public Camera();
     method public void applyToCanvas(android.graphics.Canvas);
@@ -18537,6 +18534,23 @@
 
 package android.hardware.display {
 
+  public final class DeviceProductInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectionToSinkType();
+    method public int getManufactureWeek();
+    method public int getManufactureYear();
+    method @NonNull public String getManufacturerPnpId();
+    method public int getModelYear();
+    method @Nullable public String getName();
+    method @NonNull public String getProductId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONNECTION_TO_SINK_BUILT_IN = 1; // 0x1
+    field public static final int CONNECTION_TO_SINK_DIRECT = 2; // 0x2
+    field public static final int CONNECTION_TO_SINK_TRANSITIVE = 3; // 0x3
+    field public static final int CONNECTION_TO_SINK_UNKNOWN = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.display.DeviceProductInfo> CREATOR;
+  }
+
   public final class DisplayManager {
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int);
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface, int, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
@@ -42465,7 +42479,7 @@
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -46613,6 +46627,7 @@
     method public long getAppVsyncOffsetNanos();
     method public void getCurrentSizeRange(android.graphics.Point, android.graphics.Point);
     method @Nullable public android.view.DisplayCutout getCutout();
+    method @Nullable public android.hardware.display.DeviceProductInfo getDeviceProductInfo();
     method public int getDisplayId();
     method public int getFlags();
     method public android.view.Display.HdrCapabilities getHdrCapabilities();
@@ -55055,6 +55070,8 @@
     method public void setChronometer(@IdRes int, long, String, boolean);
     method public void setChronometerCountDown(@IdRes int, boolean);
     method public void setColor(@IdRes int, @NonNull String, @ColorRes int);
+    method public void setColorInt(@IdRes int, @NonNull String, @ColorInt int, @ColorInt int);
+    method public void setColorStateList(@IdRes int, @NonNull String, @Nullable android.content.res.ColorStateList, @Nullable android.content.res.ColorStateList);
     method public void setColorStateList(@IdRes int, @NonNull String, @ColorRes int);
     method public void setCompoundButtonChecked(@IdRes int, boolean);
     method public void setContentDescription(@IdRes int, CharSequence);
@@ -55065,6 +55082,7 @@
     method public void setFloatDimen(@IdRes int, @NonNull String, @DimenRes int);
     method public void setFloatDimen(@IdRes int, @NonNull String, float, int);
     method public void setIcon(@IdRes int, String, android.graphics.drawable.Icon);
+    method public void setIcon(@IdRes int, @NonNull String, @Nullable android.graphics.drawable.Icon, @Nullable android.graphics.drawable.Icon);
     method public void setImageViewBitmap(@IdRes int, android.graphics.Bitmap);
     method public void setImageViewIcon(@IdRes int, android.graphics.drawable.Icon);
     method public void setImageViewResource(@IdRes int, @DrawableRes int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7b7518d..d79c11d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5704,7 +5704,7 @@
     method public void updateResourcePriority(int, int);
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
-    field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+    field public static final long INVALID_FILTER_ID_LONG = -1L; // 0xffffffffffffffffL
     field public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE = -1; // 0xffffffff
     field public static final int INVALID_FRONTEND_ID = -1; // 0xffffffff
     field public static final int INVALID_FRONTEND_SETTING_FREQUENCY = -1; // 0xffffffff
@@ -5885,7 +5885,7 @@
     method public int getItemFragmentIndex();
     method public int getItemId();
     method public int getLastItemFragmentIndex();
-    method public int getMpuSequenceNumber();
+    method @IntRange(from=0) public int getMpuSequenceNumber();
   }
 
   public class DownloadSettings extends android.media.tv.tuner.filter.Settings {
@@ -5903,7 +5903,7 @@
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
     method public int flush();
     method public int getId();
-    method public long getId64Bit();
+    method public long getIdLong();
     method public int read(@NonNull byte[], long, long);
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int setMonitorEventMask(int);
@@ -5994,7 +5994,7 @@
     method public long getDataLength();
     method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
     method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
-    method public int getMpuSequenceNumber();
+    method @IntRange(from=0) public int getMpuSequenceNumber();
     method public long getOffset();
     method public long getPts();
     method public int getStreamId();
@@ -6019,7 +6019,7 @@
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
     method public int getFirstMacroblockInSlice();
-    method public int getMpuSequenceNumber();
+    method @IntRange(from=0) public int getMpuSequenceNumber();
     method public long getPts();
     method public int getScHevcIndexMask();
     method public int getTsIndexMask();
@@ -6027,7 +6027,7 @@
 
   public class PesEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
-    method public int getMpuSequenceNumber();
+    method @IntRange(from=0) public int getMpuSequenceNumber();
     method public int getStreamId();
   }
 
@@ -8580,7 +8580,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
     method @NonNull public android.os.BatterySaverPolicyConfig getFullPowerSavePolicy();
-    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveModeTrigger();
+    method public int getPowerSaveModeTrigger();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
@@ -12904,6 +12904,7 @@
     field public static final String EXTRA_EMERGENCY_CALL = "e_call";
     field public static final String EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED = "android.telephony.ims.extra.EXTENDING_TO_CONFERENCE_SUPPORTED";
     field public static final String EXTRA_FORWARDED_NUMBER = "android.telephony.ims.extra.FORWARDED_NUMBER";
+    field public static final String EXTRA_IS_BUSINESS_CALL = "android.telephony.ims.extra.IS_BUSINESS_CALL";
     field public static final String EXTRA_IS_CALL_PULL = "CallPull";
     field public static final String EXTRA_IS_CROSS_SIM_CALL = "android.telephony.ims.extra.IS_CROSS_SIM_CALL";
     field public static final String EXTRA_LOCATION = "android.telephony.ims.extra.LOCATION";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e0e8453..d7b43c0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1640,6 +1640,7 @@
   public class StorageManager {
     method @NonNull public static java.util.UUID convert(@NonNull String);
     method @NonNull public static String convert(@NonNull java.util.UUID);
+    method public static boolean isUserKeyUnlocked(int);
   }
 
   public final class StorageVolume implements android.os.Parcelable {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1906ee4..b8735c7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -6553,14 +6553,15 @@
     public interface OnOpNotedListener {
         /**
          * Called when an op was noted.
-         *
          * @param code The op code.
          * @param uid The UID performing the operation.
          * @param packageName The package performing the operation.
+         * @param attributionTag The attribution tag performing the operation.
          * @param flags The flags of this op
          * @param result The result of the note.
          */
-        void onOpNoted(int code, int uid, String packageName, @OpFlags int flags, @Mode int result);
+        void onOpNoted(int code, int uid, String packageName, String attributionTag,
+                @OpFlags int flags, @Mode int result);
     }
 
     /**
@@ -6593,14 +6594,15 @@
          * Called when an op was started.
          *
          * Note: This is only for op starts. It is not called when an op is noted or stopped.
-         *
          * @param op The op code.
          * @param uid The UID performing the operation.
          * @param packageName The package performing the operation.
+         * @param attributionTag The attribution tag performing the operation.
          * @param flags The flags of this op
          * @param result The result of the start.
          */
-        void onOpStarted(int op, int uid, String packageName, @OpFlags int flags, @Mode int result);
+        void onOpStarted(int op, int uid, String packageName, String attributionTag,
+                @OpFlags int flags, @Mode int result);
     }
 
     AppOpsManager(Context context, IAppOpsService service) {
@@ -7183,8 +7185,9 @@
              }
              cb = new IAppOpsStartedCallback.Stub() {
                  @Override
-                 public void opStarted(int op, int uid, String packageName, int flags, int mode) {
-                     callback.onOpStarted(op, uid, packageName, flags, mode);
+                 public void opStarted(int op, int uid, String packageName, String attributionTag,
+                         int flags, int mode) {
+                     callback.onOpStarted(op, uid, packageName, attributionTag, flags, mode);
                  }
              };
              mStartedWatchers.put(callback, cb);
@@ -7250,8 +7253,9 @@
             }
             cb = new IAppOpsNotedCallback.Stub() {
                 @Override
-                public void opNoted(int op, int uid, String packageName, int flags, int mode) {
-                    callback.onOpNoted(op, uid, packageName, flags, mode);
+                public void opNoted(int op, int uid, String packageName, String attributionTag,
+                        int flags, int mode) {
+                    callback.onOpNoted(op, uid, packageName, attributionTag, flags, mode);
                 }
             };
             mNotedWatchers.put(callback, cb);
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 4dbff0c..721a444 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -1073,6 +1073,7 @@
     private void onOperationSafetyStateChanged(Context context, Intent intent) {
         if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON)
                 || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) {
+            Log.w(TAG, "Igoring intent that's missing required extras");
             return;
         }
 
@@ -1084,7 +1085,6 @@
         }
         boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE,
                 /* defaultValue=*/ false);
-
         onOperationSafetyStateChanged(context, reason, isSafe);
     }
 
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index b566eab..61defb5 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -16,6 +16,8 @@
 
 package android.app.time;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -31,9 +33,9 @@
 /**
  * A time signal from an External source.
  *
- * External time suggestions are for use in situations where the Android device is part of a wider
- * network of devices that are required to use a single time source, and where authority for the
- * time is external to the Android device. For example, for the Android Auto use case where the
+ * <p>External time suggestions are for use in situations where the Android device is part of a
+ * wider network of devices that are required to use a single time source, and where authority for
+ * the time is external to the Android device. For example, for the Android Auto use case where the
  * Android device is part of a wider in-car network of devices that should display the same time.
  *
  * <p>Android allows for a single external source for time. If there are several external sources
@@ -49,19 +51,19 @@
  * capture the elapsed realtime reference clock, e.g. via {@link SystemClock#elapsedRealtime()},
  * when the UTC time is first obtained (usually under a wakelock). This enables Android to adjust
  * for latency introduced between suggestion creation and eventual use. Adjustments for other
- * sources of latency, i.e. those before the external time suggestion is created, must be handled
- * by the creator.
+ * sources of latency, i.e. those before the external time suggestion is created, must be handled by
+ * the creator.
  *
- * <p>{@code utcTime} is the suggested time. The {@code utcTime.value} is the number of milliseconds
- * elapsed since 1/1/1970 00:00:00 UTC. The {@code utcTime.referenceTimeMillis} is the value of the
- * elapsed realtime clock when the {@code utcTime.value} was established.
- * Note that the elapsed realtime clock is considered accurate but it is volatile, so time
- * suggestions cannot be persisted across device resets.
+ * <p>{@code elapsedRealtimeMillis} and {@code suggestionMillis} represent the suggested time.
+ * {@code suggestionMillis} is the number of milliseconds elapsed since 1/1/1970 00:00:00 UTC.
+ * {@code elapsedRealtimeMillis} is the value of the elapsed realtime clock when {@code
+ * suggestionMillis} was established. Note that the elapsed realtime clock is considered accurate
+ * but it is volatile, so time suggestions cannot be persisted across device resets.
  *
  * <p>{@code debugInfo} contains debugging metadata associated with the suggestion. This is used to
  * record why the suggestion exists and how it was entered. This information exists only to aid in
- * debugging and therefore is used by {@link #toString()}, but it is not for use in detection
- * logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
+ * debugging and therefore is used by {@link #toString()}, but it is not for use in detection logic
+ * and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
  *
  * @hide
  */
@@ -78,17 +80,28 @@
                 }
             };
 
-    @NonNull private final TimestampedValue<Long> mUtcTime;
-    @Nullable private ArrayList<String> mDebugInfo;
+    @NonNull
+    private final TimestampedValue<Long> mUtcTime;
+    @Nullable
+    private ArrayList<String> mDebugInfo;
 
-    public ExternalTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
-        mUtcTime = Objects.requireNonNull(utcTime);
-        Objects.requireNonNull(utcTime.getValue());
+    /**
+     * Creates a time suggestion cross-referenced to the elapsed realtime clock. See {@link
+     * ExternalTimeSuggestion} for more details.
+     *
+     * @param elapsedRealtimeMillis the elapsed realtime clock reference for the suggestion
+     * @param suggestionMillis      the suggested UTC time in milliseconds since the start of the
+     *                              Unix epoch
+     */
+    public ExternalTimeSuggestion(@ElapsedRealtimeLong long elapsedRealtimeMillis,
+            @CurrentTimeMillisLong long suggestionMillis) {
+        mUtcTime = new TimestampedValue(elapsedRealtimeMillis, suggestionMillis);
     }
 
     private static ExternalTimeSuggestion createFromParcel(Parcel in) {
         TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
-        ExternalTimeSuggestion suggestion = new ExternalTimeSuggestion(utcTime);
+        ExternalTimeSuggestion suggestion =
+                new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue());
         @SuppressWarnings("unchecked")
         ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
         suggestion.mDebugInfo = debugInfo;
@@ -106,23 +119,31 @@
         dest.writeList(mDebugInfo);
     }
 
+    /**
+     * {@hide}
+     */
     @NonNull
     public TimestampedValue<Long> getUtcTime() {
         return mUtcTime;
     }
 
+    /**
+     * Returns information that can be useful for debugging / logging. See {@link #addDebugInfo}.
+     * {@hide}
+     */
     @NonNull
     public List<String> getDebugInfo() {
         return mDebugInfo == null
-                ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+                ? Collections.emptyList()
+                : Collections.unmodifiableList(mDebugInfo);
     }
 
     /**
      * Associates information with the instance that can be useful for debugging / logging. The
-     * information is present in {@link #toString()} but is not considered for
-     * {@link #equals(Object)} and {@link #hashCode()}.
+     * information is present in {@link #toString()} but is not considered for {@link
+     * #equals(Object)} and {@link #hashCode()}.
      */
-    public void addDebugInfo(String... debugInfos) {
+    public void addDebugInfo(@NonNull String... debugInfos) {
         if (mDebugInfo == null) {
             mDebugInfo = new ArrayList<>();
         }
@@ -148,9 +169,7 @@
 
     @Override
     public String toString() {
-        return "ExternalTimeSuggestion{"
-                + "mUtcTime=" + mUtcTime
-                + ", mDebugInfo=" + mDebugInfo
+        return "ExternalTimeSuggestion{" + "mUtcTime=" + mUtcTime + ", mDebugInfo=" + mDebugInfo
                 + '}';
     }
 }
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
index 262d244..430960f 100644
--- a/core/java/android/app/time/TimeManager.java
+++ b/core/java/android/app/time/TimeManager.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.timedetector.ITimeDetectorService;
 import android.app.timezonedetector.ITimeZoneDetectorService;
 import android.content.Context;
 import android.os.RemoteException;
@@ -45,6 +46,7 @@
 
     private final Object mLock = new Object();
     private final ITimeZoneDetectorService mITimeZoneDetectorService;
+    private final ITimeDetectorService mITimeDetectorService;
 
     @GuardedBy("mLock")
     private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
@@ -62,6 +64,8 @@
         // internal refactoring.
         mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+        mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
     }
 
     /**
@@ -214,4 +218,23 @@
             }
         }
     }
+
+    /**
+     * Suggests the current time from an external time source. For example, a form factor-specific
+     * HAL. This time <em>may</em> be used to set the device system clock, depending on the device
+     * configuration and user settings. This method call is processed asynchronously.
+     * See {@link ExternalTimeSuggestion} for more details.
+     * {@hide}
+     */
+    @RequiresPermission(android.Manifest.permission.SET_TIME)
+    public void suggestExternalTime(@NonNull ExternalTimeSuggestion timeSuggestion) {
+        if (DEBUG) {
+            Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
+        }
+        try {
+            mITimeDetectorService.suggestExternalTime(timeSuggestion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 76f3785..52016b6 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
-import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.TimestampedValue;
@@ -80,12 +79,4 @@
      */
     @RequiresPermission(android.Manifest.permission.SET_TIME)
     void suggestGnssTime(GnssTimeSuggestion timeSuggestion);
-
-    /**
-     * Suggests the time according to an external time source (form factor specific HAL, etc).
-     *
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.SET_TIME)
-    void suggestExternalTime(ExternalTimeSuggestion timeSuggestion);
 }
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index ef818ef..b0aa3c8 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,7 +17,6 @@
 package android.app.timedetector;
 
 import android.annotation.NonNull;
-import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -87,16 +86,4 @@
             throw e.rethrowFromSystemServer();
         }
     }
-
-    @Override
-    public void suggestExternalTime(ExternalTimeSuggestion timeSuggestion) {
-        if (DEBUG) {
-            Log.d(TAG, "suggestExternalTime called: " + timeSuggestion);
-        }
-        try {
-            mITimeDetectorService.suggestExternalTime(timeSuggestion);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 01ff432..dec2c3d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1105,19 +1105,15 @@
      * <p>
      * This property is the compile-time equivalent of
      * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
-     *
-     * @hide For platform use only; we don't expect developers to need to read this value.
      */
     public int compileSdkVersion;
 
     /**
-     * The development codename (ex. "O", "REL") of the framework against which the application
+     * The development codename (ex. "S", "REL") of the framework against which the application
      * claims to have been compiled, or {@code null} if not specified.
      * <p>
      * This property is the compile-time equivalent of
      * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
-     *
-     * @hide For platform use only; we don't expect developers to need to read this value.
      */
     @Nullable
     public String compileSdkVersionCodename;
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index bc5d14a..0d5b33c 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -24,16 +24,16 @@
 import android.content.pm.parsing.PackageLite;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
-import android.util.jar.StrictJarFile;
 import android.util.JsonReader;
 import android.util.Log;
+import android.util.jar.StrictJarFile;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -53,7 +53,10 @@
     /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */
     public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */
-    private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest";
+    private static final String PROPERTY_DM_JSON_MANIFEST_REQUIRED =
+            "pm.dexopt.dm.require_manifest";
+    /** $> adb shell 'setprop pm.dexopt.dm.require_fsverity true' */
+    private static final String PROPERTY_DM_FSVERITY_REQUIRED = "pm.dexopt.dm.require_fsverity";
 
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
 
@@ -70,6 +73,13 @@
     }
 
     /**
+     * Returns whether fs-verity is required to install a dex metadata
+     */
+    public static boolean isFsVerityRequired() {
+        return SystemProperties.getBoolean(PROPERTY_DM_FSVERITY_REQUIRED, false);
+    }
+
+    /**
      * Return the size (in bytes) of all dex metadata files associated with the given package.
      */
     public static long getPackageDexMetadataSize(PackageLite pkg) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1ffd18f..788afe3 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -667,7 +667,7 @@
     private abstract static class BaseEventQueue {
         private static native long nativeInitBaseEventQueue(long nativeManager,
                 WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
-                String packageName, int mode, String opPackageName);
+                String packageName, int mode, String opPackageName, String attributionTag);
         private static native int nativeEnableSensor(long eventQ, int handle, int rateUs,
                 int maxBatchReportLatencyUs);
         private static native int nativeDisableSensor(long eventQ, int handle);
@@ -689,7 +689,8 @@
             if (packageName == null) packageName = "";
             mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
                     new WeakReference<>(this), looper.getQueue(),
-                    packageName, mode, manager.mContext.getOpPackageName());
+                    packageName, mode, manager.mContext.getOpPackageName(),
+                    manager.mContext.getAttributionTag());
             mCloseGuard.open("dispose");
             mManager = manager;
         }
diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java
index 41126b7..9457d8f1 100644
--- a/core/java/android/hardware/display/DeviceProductInfo.java
+++ b/core/java/android/hardware/display/DeviceProductInfo.java
@@ -16,40 +16,69 @@
 
 package android.hardware.display;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Arrays;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
  * Product-specific information about the display or the directly connected device on the
  * display chain. For example, if the display is transitively connected, this field may contain
  * product information about the intermediate device.
- * @hide
  */
 public final class DeviceProductInfo implements Parcelable {
+    /** @hide */
+    @IntDef(prefix = {"CONNECTION_TO_SINK_"}, value = {
+            CONNECTION_TO_SINK_UNKNOWN,
+            CONNECTION_TO_SINK_BUILT_IN,
+            CONNECTION_TO_SINK_DIRECT,
+            CONNECTION_TO_SINK_TRANSITIVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConnectionToSinkType { }
+
+    /** The device connection to the display sink is unknown. */
+    public static final int CONNECTION_TO_SINK_UNKNOWN =
+            IDeviceProductInfoConstants.CONNECTION_TO_SINK_UNKNOWN;
+
+    /** The display sink is built-in to the device */
+    public static final int CONNECTION_TO_SINK_BUILT_IN =
+            IDeviceProductInfoConstants.CONNECTION_TO_SINK_BUILT_IN;
+
+    /** The device is directly connected to the display sink. */
+    public static final int CONNECTION_TO_SINK_DIRECT =
+            IDeviceProductInfoConstants.CONNECTION_TO_SINK_DIRECT;
+
+    /** The device is transitively connected to the display sink. */
+    public static final int CONNECTION_TO_SINK_TRANSITIVE =
+            IDeviceProductInfoConstants.CONNECTION_TO_SINK_TRANSITIVE;
+
     private final String mName;
     private final String mManufacturerPnpId;
     private final String mProductId;
     private final Integer mModelYear;
     private final ManufactureDate mManufactureDate;
-    private final int[] mRelativeAddress;
+    private final @ConnectionToSinkType int mConnectionToSinkType;
 
+    /** @hide */
     public DeviceProductInfo(
             String name,
             String manufacturerPnpId,
             String productId,
             Integer modelYear,
             ManufactureDate manufactureDate,
-            int[] relativeAddress) {
+            int connectionToSinkType) {
         this.mName = name;
         this.mManufacturerPnpId = manufacturerPnpId;
         this.mProductId = productId;
         this.mModelYear = modelYear;
         this.mManufactureDate = manufactureDate;
-        this.mRelativeAddress = relativeAddress;
+        this.mConnectionToSinkType = connectionToSinkType;
     }
 
     private DeviceProductInfo(Parcel in) {
@@ -58,12 +87,13 @@
         mProductId = (String) in.readValue(null);
         mModelYear = (Integer) in.readValue(null);
         mManufactureDate = (ManufactureDate) in.readValue(null);
-        mRelativeAddress = in.createIntArray();
+        mConnectionToSinkType = in.readInt();
     }
 
     /**
      * @return Display name.
      */
+    @Nullable
     public String getName() {
         return mName;
     }
@@ -71,6 +101,7 @@
     /**
      * @return Manufacturer Plug and Play ID.
      */
+    @NonNull
     public String getManufacturerPnpId() {
         return mManufacturerPnpId;
     }
@@ -78,32 +109,58 @@
     /**
      * @return Manufacturer product ID.
      */
+    @NonNull
     public String getProductId() {
         return mProductId;
     }
 
     /**
-     * @return Model year of the device. Typically exactly one of model year or
-     *      manufacture date will be present.
+     * @return Model year of the device. Return -1 if not available. Typically,
+     * one of model year or manufacture year is available.
      */
-    public Integer getModelYear() {
-        return mModelYear;
+    public int getModelYear()  {
+        return mModelYear != null ? mModelYear : -1;
+    }
+
+    /**
+     * @return The year of manufacture, or -1 it is not available. Typically,
+     * one of model year or manufacture year is available.
+     */
+    public int getManufactureYear()  {
+        if (mManufactureDate == null) {
+            return -1;
+        }
+        return mManufactureDate.mYear != null ? mManufactureDate.mYear : -1;
+    }
+
+    /**
+     * @return The week of manufacture, or -1 it is not available. Typically,
+     * not present if model year is available.
+     */
+    public int getManufactureWeek() {
+        if (mManufactureDate == null) {
+            return -1;
+        }
+        return mManufactureDate.mWeek != null ?  mManufactureDate.mWeek : -1;
     }
 
     /**
      * @return Manufacture date. Typically exactly one of model year or manufacture
      * date will be present.
+     *
+     * @hide
      */
     public ManufactureDate getManufactureDate() {
         return mManufactureDate;
     }
 
     /**
-     * @return Relative address in the display network. For example, for HDMI connected devices this
-     * can be its physical address. Each component of the address is in the range [0, 255].
+     * @return How the current device is connected to the display sink. For example, the display
+     * can be connected immediately to the device or there can be a receiver in between.
      */
-    public int[] getRelativeAddress() {
-        return mRelativeAddress;
+    @ConnectionToSinkType
+    public int getConnectionToSinkType() {
+        return mConnectionToSinkType;
     }
 
     @Override
@@ -119,8 +176,8 @@
                 + mModelYear
                 + ", manufactureDate="
                 + mManufactureDate
-                + ", relativeAddress="
-                + Arrays.toString(mRelativeAddress)
+                + ", connectionToSinkType="
+                + mConnectionToSinkType
                 + '}';
     }
 
@@ -134,16 +191,16 @@
                 && Objects.equals(mProductId, that.mProductId)
                 && Objects.equals(mModelYear, that.mModelYear)
                 && Objects.equals(mManufactureDate, that.mManufactureDate)
-                && Arrays.equals(mRelativeAddress, that.mRelativeAddress);
+                && mConnectionToSinkType == that.mConnectionToSinkType;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mName, mManufacturerPnpId, mProductId, mModelYear, mManufactureDate,
-            Arrays.hashCode(mRelativeAddress));
+                mConnectionToSinkType);
     }
 
-    public static final Creator<DeviceProductInfo> CREATOR =
+    @NonNull public static final Creator<DeviceProductInfo> CREATOR =
             new Creator<DeviceProductInfo>() {
                 @Override
                 public DeviceProductInfo createFromParcel(Parcel in) {
@@ -162,13 +219,13 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mName);
         dest.writeString(mManufacturerPnpId);
         dest.writeValue(mProductId);
         dest.writeValue(mModelYear);
         dest.writeValue(mManufactureDate);
-        dest.writeIntArray(mRelativeAddress);
+        dest.writeInt(mConnectionToSinkType);
     }
 
     /**
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index ff9b4f4..e5163d8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1881,6 +1881,10 @@
      * Returns the current battery saver control mode. Values it may return are defined in
      * AutoPowerSaveModeTriggers. Note that this is a global device state, not a per user setting.
      *
+     * <p>Note: Prior to Android version {@link Build.VERSION_CODES#S}, any app calling this method
+     * was required to hold the {@link android.Manifest.permission#POWER_SAVER} permission. Starting
+     * from Android version {@link Build.VERSION_CODES#S}, that permission is no longer required.
+     *
      * @return The current value power saver mode for the system.
      *
      * @see AutoPowerSaveModeTriggers
@@ -1889,7 +1893,6 @@
      */
     @AutoPowerSaveModeTriggers
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.POWER_SAVER)
     public int getPowerSaveModeTrigger() {
         try {
             return mService.getPowerSaveModeTrigger();
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 54d2df8..136dc38 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -207,6 +207,12 @@
     public static final int SE_UID = 1068;
 
     /**
+     * Defines the UID/GID for the iorapd.
+     * @hide
+     */
+    public static final int IORAPD_UID = 1071;
+
+    /**
      * Defines the UID/GID for the NetworkStack app.
      * @hide
      */
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a5d3c2a..3a5426c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1536,6 +1536,7 @@
     }
 
     /** {@hide} */
+    @TestApi
     public static boolean isUserKeyUnlocked(int userId) {
         if (sStorageManager == null) {
             sStorageManager = IStorageManager.Stub
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e979e13..6ee7eb2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14614,6 +14614,29 @@
         public static final String POWER_BUTTON_VERY_LONG_PRESS =
                 "power_button_very_long_press";
 
+
+        /**
+         * Keyguard should be on the left hand side of the screen, for wide screen layouts.
+         *
+         * @hide
+         */
+        public static final int ONE_HANDED_KEYGUARD_SIDE_LEFT = 0;
+
+        /**
+         * Keyguard should be on the right hand side of the screen, for wide screen layouts.
+         *
+         * @hide
+         */
+        public static final int ONE_HANDED_KEYGUARD_SIDE_RIGHT = 1;
+        /**
+         * In one handed mode, which side the keyguard should be on. Allowable values are one of
+         * the ONE_HANDED_KEYGUARD_SIDE_* constants.
+         *
+         * @hide
+         */
+        @Readable
+        public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side";
+
         /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0ba1dfe..8117c96 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -34,6 +34,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
@@ -1181,6 +1182,18 @@
     }
 
     /**
+     * Returns the product-specific information about the display or the directly connected
+     * device on the display chain.
+     * For example, if the display is transitively connected, this field may contain product
+     * information about the intermediate device.
+     * Returns {@code null} if product information is not available.
+     */
+    @Nullable
+    public DeviceProductInfo getDeviceProductInfo() {
+        return mDisplayInfo.deviceProductInfo;
+    }
+
+    /**
      * Gets display metrics that describe the size and density of this display.
      * The size returned by this method does not necessarily represent the
      * actual raw size (native resolution) of the display.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 219190f..21dd1fb 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -20,8 +20,6 @@
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
-import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
-import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
@@ -106,14 +104,11 @@
     public static final int ITYPE_NAVIGATION_BAR = 1;
     public static final int ITYPE_CAPTION_BAR = 2;
 
-    // The always visible types are visible to all windows regardless of the z-order.
-    public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3;
-    public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE;
+    public static final int ITYPE_TOP_GESTURES = 3;
     public static final int ITYPE_BOTTOM_GESTURES = 4;
     public static final int ITYPE_LEFT_GESTURES = 5;
     public static final int ITYPE_RIGHT_GESTURES = 6;
 
-    /** Additional gesture inset types that map into {@link Type.MANDATORY_SYSTEM_GESTURES}. */
     public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
     public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
     public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
@@ -123,7 +118,6 @@
     public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
     public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
     public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
-    public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT;
 
     public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
     public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
@@ -188,18 +182,6 @@
     }
 
     /**
-     * Mirror the always visible sources from the other state. They will share the same object for
-     * the always visible types.
-     *
-     * @param other the state to mirror the mirrored sources from.
-     */
-    public void mirrorAlwaysVisibleInsetsSources(InsetsState other) {
-        for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) {
-            mSources[type] = other.mSources[type];
-        }
-    }
-
-    /**
      * Calculates {@link WindowInsets} based on the current source configuration.
      *
      * @param frame The frame to calculate the insets relative to.
@@ -380,14 +362,14 @@
         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
                 insets, type);
 
-        if (type == MANDATORY_SYSTEM_GESTURES) {
+        if (type == Type.MANDATORY_SYSTEM_GESTURES) {
             // Mandatory system gestures are also system gestures.
             // TODO: find a way to express this more generally. One option would be to define
             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
             //       ability to set systemGestureInsets() independently from
             //       mandatorySystemGestureInsets() in the Builder.
             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
-                    insets, SYSTEM_GESTURES);
+                    insets, Type.SYSTEM_GESTURES);
         }
     }
 
@@ -493,9 +475,14 @@
      * to the client.
      *
      * @param type The {@link InternalInsetsType} of the source to remove
+     * @return {@code true} if this InsetsState was modified; {@code false} otherwise.
      */
-    public void removeSource(@InternalInsetsType int type) {
+    public boolean removeSource(@InternalInsetsType int type) {
+        if (mSources[type] == null) {
+            return false;
+        }
         mSources[type] = null;
+        return true;
     }
 
     /**
@@ -552,6 +539,24 @@
         }
     }
 
+    /**
+     * Sets the values from the other InsetsState. But for sources, only specific types of source
+     * would be set.
+     *
+     * @param other the other InsetsState.
+     * @param types the only types of sources would be set.
+     */
+    public void set(InsetsState other, @InsetsType int types) {
+        mDisplayFrame.set(other.mDisplayFrame);
+        mDisplayCutout.set(other.mDisplayCutout);
+        mRoundedCorners = other.getRoundedCorners();
+        final ArraySet<Integer> t = toInternalType(types);
+        for (int i = t.size() - 1; i >= 0; i--) {
+            final int type = t.valueAt(i);
+            mSources[type] = other.mSources[type];
+        }
+    }
+
     public void addSource(InsetsSource source) {
         mSources[source.getType()] = source;
     }
@@ -575,6 +580,18 @@
         if ((types & Type.CAPTION_BAR) != 0) {
             result.add(ITYPE_CAPTION_BAR);
         }
+        if ((types & Type.SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_GESTURES);
+            result.add(ITYPE_TOP_GESTURES);
+            result.add(ITYPE_RIGHT_GESTURES);
+            result.add(ITYPE_BOTTOM_GESTURES);
+        }
+        if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_MANDATORY_GESTURES);
+            result.add(ITYPE_TOP_MANDATORY_GESTURES);
+            result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
+            result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
+        }
         if ((types & Type.DISPLAY_CUTOUT) != 0) {
             result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
             result.add(ITYPE_TOP_DISPLAY_CUTOUT);
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 015e804..569c287 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -335,7 +335,7 @@
         }
         if (o instanceof RoundedCorners) {
             RoundedCorners r = (RoundedCorners) o;
-            return Arrays.deepEquals(mRoundedCorners, ((RoundedCorners) o).mRoundedCorners);
+            return Arrays.deepEquals(mRoundedCorners, r.mRoundedCorners);
         }
         return false;
     }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 66b9617..2bd32ac 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -68,6 +68,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -141,6 +142,9 @@
             int layerStack);
     private static native void nativeSetBlurRegions(long transactionObj, long nativeObj,
             float[][] regions, int length);
+    private static native void nativeSetStretchEffect(long transactionObj, long nativeObj,
+            float left, float top, float right, float bottom, float vecX, float vecY,
+            float maxStretchAmount);
 
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -161,25 +165,21 @@
             int L, int T, int R, int B);
     private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
             int width, int height);
-    private static native DisplayInfo nativeGetDisplayInfo(IBinder displayToken);
-    private static native DisplayMode[] nativeGetDisplayModes(
-            IBinder displayToken);
+    private static native StaticDisplayInfo nativeGetStaticDisplayInfo(IBinder displayToken);
+    private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(IBinder displayToken);
     private static native DisplayedContentSamplingAttributes
             nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
     private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
             boolean enable, int componentMask, int maxFrames);
     private static native DisplayedContentSample nativeGetDisplayedContentSample(
             IBinder displayToken, long numFrames, long timestamp);
-    private static native int nativeGetActiveDisplayMode(IBinder displayToken);
     private static native boolean nativeSetDesiredDisplayModeSpecs(IBinder displayToken,
             DesiredDisplayModeSpecs desiredDisplayModeSpecs);
     private static native DesiredDisplayModeSpecs
             nativeGetDesiredDisplayModeSpecs(IBinder displayToken);
-    private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
     private static native DisplayPrimaries nativeGetDisplayNativePrimaries(
             IBinder displayToken);
     private static native int[] nativeGetCompositionDataspaces();
-    private static native int nativeGetActiveColorMode(IBinder displayToken);
     private static native boolean nativeSetActiveColorMode(IBinder displayToken,
             int colorMode);
     private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on);
@@ -191,8 +191,6 @@
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
 
-    private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
-
     private static native boolean nativeGetAutoLowLatencyModeSupport(IBinder displayToken);
     private static native boolean nativeGetGameContentTypeSupport(IBinder displayToken);
 
@@ -1707,7 +1705,7 @@
      *
      * @hide
      */
-    public static final class DisplayInfo {
+    public static final class StaticDisplayInfo {
         public boolean isInternal;
         public float density;
         public boolean secure;
@@ -1715,7 +1713,7 @@
 
         @Override
         public String toString() {
-            return "DisplayInfo{isInternal=" + isInternal
+            return "StaticDisplayInfo{isInternal=" + isInternal
                     + ", density=" + density
                     + ", secure=" + secure
                     + ", deviceProductInfo=" + deviceProductInfo + "}";
@@ -1725,7 +1723,7 @@
         public boolean equals(@Nullable Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
-            DisplayInfo that = (DisplayInfo) o;
+            StaticDisplayInfo that = (StaticDisplayInfo) o;
             return isInternal == that.isInternal
                     && density == that.density
                     && secure == that.secure
@@ -1739,6 +1737,49 @@
     }
 
     /**
+     * Dynamic information about physical display.
+     *
+     * @hide
+     */
+    public static final class DynamicDisplayInfo {
+        public DisplayMode[] supportedDisplayModes;
+        public int activeDisplayModeId;
+
+        public int[] supportedColorModes;
+        public int activeColorMode;
+
+        public Display.HdrCapabilities hdrCapabilities;
+
+        @Override
+        public String toString() {
+            return "DynamicDisplayInfo{"
+                    + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
+                    + ", activeDisplayModeId=" + activeDisplayModeId
+                    + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
+                    + ", activeColorMode=" + activeColorMode
+                    + ", hdrCapabilities=" + hdrCapabilities + "}";
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DynamicDisplayInfo that = (DynamicDisplayInfo) o;
+            return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
+                && activeDisplayModeId == that.activeDisplayModeId
+                && Arrays.equals(supportedColorModes, that.supportedColorModes)
+                && activeColorMode == that.activeColorMode
+                && Objects.equals(hdrCapabilities, that.hdrCapabilities);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(supportedDisplayModes, activeDisplayModeId, activeDisplayModeId,
+                    activeColorMode, hdrCapabilities);
+        }
+    }
+
+    /**
      * Configuration supported by physical display.
      *
      * @hide
@@ -1749,6 +1790,7 @@
          */
         public static final int INVALID_DISPLAY_MODE_ID = -1;
 
+        public int id;
         public int width;
         public int height;
         public float xDpi;
@@ -1768,7 +1810,8 @@
 
         @Override
         public String toString() {
-            return "DisplayConfig{width=" + width
+            return "DisplayMode{id=" + id
+                    + ", width=" + width
                     + ", height=" + height
                     + ", xDpi=" + xDpi
                     + ", yDpi=" + yDpi
@@ -1777,6 +1820,28 @@
                     + ", presentationDeadlineNanos=" + presentationDeadlineNanos
                     + ", group=" + group + "}";
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DisplayMode that = (DisplayMode) o;
+            return id == that.id
+                    && width == that.width
+                    && height == that.height
+                    && Float.compare(that.xDpi, xDpi) == 0
+                    && Float.compare(that.yDpi, yDpi) == 0
+                    && Float.compare(that.refreshRate, refreshRate) == 0
+                    && appVsyncOffsetNanos == that.appVsyncOffsetNanos
+                    && presentationDeadlineNanos == that.presentationDeadlineNanos
+                    && group == that.group;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
+                    presentationDeadlineNanos, group);
+        }
     }
 
     /**
@@ -1792,31 +1857,21 @@
     /**
      * @hide
      */
-    public static SurfaceControl.DisplayInfo getDisplayInfo(IBinder displayToken) {
+    public static StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-        return nativeGetDisplayInfo(displayToken);
+        return nativeGetStaticDisplayInfo(displayToken);
     }
 
     /**
      * @hide
      */
-    public static DisplayMode[] getDisplayModes(IBinder displayToken) {
+    public static DynamicDisplayInfo getDynamicDisplayInfo(IBinder displayToken) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-        return nativeGetDisplayModes(displayToken);
-    }
-
-    /**
-     * @hide
-     */
-    public static int getActiveDisplayMode(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetActiveDisplayMode(displayToken);
+        return nativeGetDynamicDisplayInfo(displayToken);
     }
 
     /**
@@ -1978,16 +2033,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static int[] getDisplayColorModes(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetDisplayColorModes(displayToken);
-    }
-
-    /**
      * Color coordinates in CIE1931 XYZ color space
      *
      * @hide
@@ -2057,16 +2102,6 @@
     /**
      * @hide
      */
-    public static int getActiveColorMode(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetActiveColorMode(displayToken);
-    }
-
-    /**
-     * @hide
-     */
     public static boolean setActiveColorMode(IBinder displayToken, int colorMode) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
@@ -2169,16 +2204,6 @@
     /**
      * @hide
      */
-    public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetHdrCapabilities(displayToken);
-    }
-
-    /**
-     * @hide
-     */
     public static boolean getAutoLowLatencyModeSupport(IBinder displayToken) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
@@ -2951,6 +2976,17 @@
         /**
          * @hide
          */
+        public Transaction setStretchEffect(SurfaceControl sc, float left, float top, float right,
+                float bottom, float vecX, float vecY, float maxStretchAmount) {
+            checkPreconditions(sc);
+            nativeSetStretchEffect(mNativeObject, sc.mNativeObject, left, top, right, bottom,
+                    vecX, vecY, maxStretchAmount);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
         public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
             checkPreconditions(sc);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 6eba83f..ec7e4c1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1444,6 +1444,14 @@
         }
 
         @Override
+        public void applyStretch(long frameNumber, float left, float top, float right,
+                float bottom, float vecX, float vecY, float maxStretch) {
+            mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
+                    maxStretch);
+            applyRtTransaction(frameNumber);
+        }
+
+        @Override
         public void positionLost(long frameNumber) {
             if (DEBUG) {
                 Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index decbf8c..4f0c568 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1975,7 +1975,10 @@
         if (client == null) {
             return false;
         }
-
+        if (mService == null) {
+            Log.w(TAG, "Autofill service is null!");
+            return false;
+        }
         if (mServiceClient == null) {
             mServiceClient = new AutofillManagerClient(this);
             try {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index d5f9774..05b177e 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -715,7 +715,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769408)
-    private EdgeEffect mEdgeGlowTop = new EdgeEffect(mContext);
+    private EdgeEffect mEdgeGlowTop;
 
     /**
      * Tracks the state of the bottom edge glow.
@@ -725,7 +725,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768444)
-    private EdgeEffect mEdgeGlowBottom = new EdgeEffect(mContext);
+    private EdgeEffect mEdgeGlowBottom;
 
     /**
      * An estimate of how many pixels are between the top of the list and
@@ -847,6 +847,8 @@
 
     public AbsListView(Context context) {
         super(context);
+        mEdgeGlowBottom = new EdgeEffect(context);
+        mEdgeGlowTop = new EdgeEffect(context);
         initAbsListView();
 
         mOwnerThread = Thread.currentThread();
@@ -867,6 +869,8 @@
 
     public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mEdgeGlowBottom = new EdgeEffect(context, attrs);
+        mEdgeGlowTop = new EdgeEffect(context, attrs);
         initAbsListView();
 
         mOwnerThread = Thread.currentThread();
@@ -3584,6 +3588,9 @@
                 mLastY != Integer.MIN_VALUE ? y - mLastY + scrollConsumedCorrection : deltaY;
         int lastYCorrection = 0;
 
+        // First allow releasing existing overscroll effect:
+        incrementalDeltaY = releaseGlow(incrementalDeltaY, x);
+
         if (mTouchMode == TOUCH_MODE_SCROLL) {
             if (PROFILE_SCROLLING) {
                 if (!mScrollProfilingStarted) {
@@ -3666,14 +3673,14 @@
                                     mTouchMode = TOUCH_MODE_OVERSCROLL;
                                 }
                                 if (incrementalDeltaY > 0) {
-                                    mEdgeGlowTop.onPull((float) -overscroll / getHeight(),
+                                    mEdgeGlowTop.onPullDistance((float) -overscroll / getHeight(),
                                             (float) x / getWidth());
                                     if (!mEdgeGlowBottom.isFinished()) {
                                         mEdgeGlowBottom.onRelease();
                                     }
                                     invalidateTopGlow();
                                 } else if (incrementalDeltaY < 0) {
-                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight(),
+                                    mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(),
                                             1.f - (float) x / getWidth());
                                     if (!mEdgeGlowTop.isFinished()) {
                                         mEdgeGlowTop.onRelease();
@@ -3713,14 +3720,15 @@
                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
                                     !contentFits())) {
                         if (rawDeltaY > 0) {
-                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight(),
+                            mEdgeGlowTop.onPullDistance((float) overScrollDistance / getHeight(),
                                     (float) x / getWidth());
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
                             invalidateTopGlow();
                         } else if (rawDeltaY < 0) {
-                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight(),
+                            mEdgeGlowBottom.onPullDistance(
+                                    (float) -overScrollDistance / getHeight(),
                                     1.f - (float) x / getWidth());
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
@@ -3757,6 +3765,44 @@
         }
     }
 
+    /**
+     * If the edge glow is currently active, this consumes part or all of deltaY
+     * on the edge glow.
+     *
+     * @param deltaY The pointer motion, in pixels, in the vertical direction, positive
+     *                         for moving down and negative for moving up.
+     * @param x The horizontal position of the pointer.
+     * @return The remainder of <code>deltaY</code> that has not been consumed by the
+     * edge glow.
+     */
+    private int releaseGlow(int deltaY, int x) {
+        // First allow releasing existing overscroll effect:
+        float consumed = 0;
+        if (mEdgeGlowTop.getDistance() != 0) {
+            consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
+                    (float) x / getWidth());
+            if (consumed != 0f) {
+                invalidateTopGlow();
+            }
+        } else if (mEdgeGlowBottom.getDistance() != 0) {
+            consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
+                    1f - (float) x / getWidth());
+            if (consumed != 0f) {
+                invalidateBottomGlow();
+            }
+        }
+        int pixelsConsumed = Math.round(consumed * getHeight());
+        return deltaY - pixelsConsumed;
+    }
+
+    /**
+     * @return <code>true</code> if either the top or bottom edge glow is currently active or
+     * <code>false</code> if it has no value to release.
+     */
+    private boolean isGlowActive() {
+        return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
+    }
+
     private void invalidateTopGlow() {
         if (!shouldDisplayEdgeEffects()) {
             return;
@@ -3926,7 +3972,9 @@
 
         if (mTouchMode == TOUCH_MODE_OVERFLING) {
             // Stopped the fling. It is a scroll.
-            mFlingRunnable.endFling();
+            if (mFlingRunnable != null) {
+                mFlingRunnable.endFling();
+            }
             if (mPositionScroller != null) {
                 mPositionScroller.stop();
             }
@@ -3936,6 +3984,7 @@
             mLastY = mMotionY;
             mMotionCorrection = 0;
             mDirection = 0;
+            stopEdgeGlowRecede(ev.getX());
         } else {
             final int x = (int) ev.getX();
             final int y = (int) ev.getY();
@@ -3948,7 +3997,10 @@
                     mTouchMode = TOUCH_MODE_SCROLL;
                     mMotionCorrection = 0;
                     motionPosition = findMotionRow(y);
-                    mFlingRunnable.flywheelTouch();
+                    if (mFlingRunnable != null) {
+                        mFlingRunnable.flywheelTouch();
+                    }
+                    stopEdgeGlowRecede(x);
                 } else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
                     // User clicked on an actual view (and was not stopping a
                     // fling). It might be a click or a scroll. Assume it is a
@@ -3984,6 +4036,15 @@
         }
     }
 
+    private void stopEdgeGlowRecede(float x) {
+        if (mEdgeGlowTop.getDistance() != 0) {
+            mEdgeGlowTop.onPullDistance(0, x / getWidth());
+        }
+        if (mEdgeGlowBottom.getDistance() != 0) {
+            mEdgeGlowBottom.onPullDistance(0, x / getWidth());
+        }
+    }
+
     private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
         if (mHasPerformedLongPress) {
             // Consume all move events following a successful long press.
@@ -4489,73 +4550,76 @@
         }
 
         switch (actionMasked) {
-        case MotionEvent.ACTION_DOWN: {
-            int touchMode = mTouchMode;
-            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
-                mMotionCorrection = 0;
-                return true;
-            }
-
-            final int x = (int) ev.getX();
-            final int y = (int) ev.getY();
-            mActivePointerId = ev.getPointerId(0);
-
-            int motionPosition = findMotionRow(y);
-            if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
-                // User clicked on an actual view (and was not stopping a fling).
-                // Remember where the motion event started
-                v = getChildAt(motionPosition - mFirstPosition);
-                mMotionViewOriginalTop = v.getTop();
-                mMotionX = x;
-                mMotionY = y;
-                mMotionPosition = motionPosition;
-                mTouchMode = TOUCH_MODE_DOWN;
-                clearScrollingCache();
-            }
-            mLastY = Integer.MIN_VALUE;
-            initOrResetVelocityTracker();
-            mVelocityTracker.addMovement(ev);
-            mNestedYOffset = 0;
-            startNestedScroll(SCROLL_AXIS_VERTICAL);
-            if (touchMode == TOUCH_MODE_FLING) {
-                return true;
-            }
-            break;
-        }
-
-        case MotionEvent.ACTION_MOVE: {
-            switch (mTouchMode) {
-            case TOUCH_MODE_DOWN:
-                int pointerIndex = ev.findPointerIndex(mActivePointerId);
-                if (pointerIndex == -1) {
-                    pointerIndex = 0;
-                    mActivePointerId = ev.getPointerId(pointerIndex);
+            case MotionEvent.ACTION_DOWN: {
+                int touchMode = mTouchMode;
+                if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
+                    mMotionCorrection = 0;
+                    return true;
                 }
-                final int y = (int) ev.getY(pointerIndex);
-                initVelocityTrackerIfNotExists();
+
+                final int x = (int) ev.getX();
+                final int y = (int) ev.getY();
+                mActivePointerId = ev.getPointerId(0);
+
+                int motionPosition = findMotionRow(y);
+                if (isGlowActive()) {
+                    // Pressed during edge effect, so this is considered the same as a fling catch.
+                    mTouchMode = TOUCH_MODE_FLING;
+                } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
+                    // User clicked on an actual view (and was not stopping a fling).
+                    // Remember where the motion event started
+                    v = getChildAt(motionPosition - mFirstPosition);
+                    mMotionViewOriginalTop = v.getTop();
+                    mMotionX = x;
+                    mMotionY = y;
+                    mMotionPosition = motionPosition;
+                    mTouchMode = TOUCH_MODE_DOWN;
+                    clearScrollingCache();
+                }
+                mLastY = Integer.MIN_VALUE;
+                initOrResetVelocityTracker();
                 mVelocityTracker.addMovement(ev);
-                if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
+                mNestedYOffset = 0;
+                startNestedScroll(SCROLL_AXIS_VERTICAL);
+                if (touchMode == TOUCH_MODE_FLING) {
                     return true;
                 }
                 break;
             }
-            break;
-        }
 
-        case MotionEvent.ACTION_CANCEL:
-        case MotionEvent.ACTION_UP: {
-            mTouchMode = TOUCH_MODE_REST;
-            mActivePointerId = INVALID_POINTER;
-            recycleVelocityTracker();
-            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-            stopNestedScroll();
-            break;
-        }
+            case MotionEvent.ACTION_MOVE: {
+                switch (mTouchMode) {
+                    case TOUCH_MODE_DOWN:
+                        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                        if (pointerIndex == -1) {
+                            pointerIndex = 0;
+                            mActivePointerId = ev.getPointerId(pointerIndex);
+                        }
+                        final int y = (int) ev.getY(pointerIndex);
+                        initVelocityTrackerIfNotExists();
+                        mVelocityTracker.addMovement(ev);
+                        if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
+                            return true;
+                        }
+                        break;
+                }
+                break;
+            }
 
-        case MotionEvent.ACTION_POINTER_UP: {
-            onSecondaryPointerUp(ev);
-            break;
-        }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP: {
+                mTouchMode = TOUCH_MODE_REST;
+                mActivePointerId = INVALID_POINTER;
+                recycleVelocityTracker();
+                reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+                stopNestedScroll();
+                break;
+            }
+
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(ev);
+                break;
+            }
         }
 
         return false;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 2112fb1..30bf546e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -211,6 +211,7 @@
     private static final int SET_RADIO_GROUP_CHECKED = 27;
     private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
     private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29;
+    private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -1877,6 +1878,73 @@
         }
     }
 
+    private final class NightModeReflectionAction extends BaseReflectionAction {
+
+        private final Object mLightValue;
+        private final Object mDarkValue;
+
+        NightModeReflectionAction(
+                @IdRes int viewId,
+                String methodName,
+                int type,
+                Object lightValue,
+                Object darkValue) {
+            super(viewId, methodName, type);
+            mLightValue = lightValue;
+            mDarkValue = darkValue;
+        }
+
+        NightModeReflectionAction(Parcel in) {
+            super(in);
+            switch (this.type) {
+                case ICON:
+                    mLightValue = in.readTypedObject(Icon.CREATOR);
+                    mDarkValue = in.readTypedObject(Icon.CREATOR);
+                    break;
+                case COLOR_STATE_LIST:
+                    mLightValue = in.readTypedObject(ColorStateList.CREATOR);
+                    mDarkValue = in.readTypedObject(ColorStateList.CREATOR);
+                    break;
+                case INT:
+                    mLightValue = in.readInt();
+                    mDarkValue = in.readInt();
+                    break;
+                default:
+                    throw new ActionException("Unexpected night mode action type: " + this.type);
+            }
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            switch (this.type) {
+                case ICON:
+                case COLOR_STATE_LIST:
+                    out.writeTypedObject((Parcelable) mLightValue, flags);
+                    out.writeTypedObject((Parcelable) mDarkValue, flags);
+                    break;
+                case INT:
+                    out.writeInt((int) mLightValue);
+                    out.writeInt((int) mDarkValue);
+                    break;
+            }
+        }
+
+        @Nullable
+        @Override
+        protected Object getParameterValue(@Nullable View view) throws ActionException {
+            if (view == null) return null;
+
+            Configuration configuration = view.getResources().getConfiguration();
+            return configuration.isNightModeActive() ? mDarkValue : mLightValue;
+        }
+
+        @Override
+        public int getActionTag() {
+            return NIGHT_MODE_REFLECTION_ACTION_TAG;
+        }
+    }
+
     /**
      * This is only used for async execution of actions and it not parcelable.
      */
@@ -3243,6 +3311,8 @@
                 return new SetViewOutlinePreferredRadiusAction(parcel);
             case SET_ON_CHECKED_CHANGE_RESPONSE_TAG:
                 return new SetOnCheckedChangeResponse(parcel);
+            case NIGHT_MODE_REFLECTION_ACTION_TAG:
+                return new NightModeReflectionAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -4130,6 +4200,30 @@
                 ResourceReflectionAction.COLOR_RESOURCE, colorResource));
     }
 
+    /**
+     * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param notNight The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_NO}
+     * @param night The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_YES}
+     */
+    public void setColorInt(
+            @IdRes int viewId,
+            @NonNull String methodName,
+            @ColorInt int notNight,
+            @ColorInt int night) {
+        addAction(
+                new NightModeReflectionAction(
+                        viewId,
+                        methodName,
+                        BaseReflectionAction.INT,
+                        notNight,
+                        night));
+    }
+
 
     /**
      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
@@ -4148,6 +4242,30 @@
     /**
      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
      *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param notNight The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_NO}
+     * @param night The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_YES}
+     */
+    public void setColorStateList(
+            @IdRes int viewId,
+            @NonNull String methodName,
+            @Nullable ColorStateList notNight,
+            @Nullable ColorStateList night) {
+        addAction(
+                new NightModeReflectionAction(
+                        viewId,
+                        methodName,
+                        BaseReflectionAction.COLOR_STATE_LIST,
+                        notNight,
+                        night));
+    }
+
+    /**
+     * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
+     *
      * The ColorStateList will be resolved from the resources at the time of inflation.
      *
      * @param viewId The id of the view on which to call the method.
@@ -4356,6 +4474,30 @@
     }
 
     /**
+     * Call a method taking one Icon on a view in the layout for this RemoteViews.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param notNight The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_NO}
+     * @param night The value to pass to the method when the view's configuration is set to
+     *                 {@link Configuration#UI_MODE_NIGHT_YES}
+     */
+    public void setIcon(
+            @IdRes int viewId,
+            @NonNull String methodName,
+            @Nullable Icon notNight,
+            @Nullable Icon night) {
+        addAction(
+                new NightModeReflectionAction(
+                        viewId,
+                        methodName,
+                        BaseReflectionAction.ICON,
+                        notNight,
+                        night));
+    }
+
+    /**
      * Equivalent to calling View.setContentDescription(CharSequence).
      *
      * @param viewId The id of the view whose content description should change.
diff --git a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
index cb280cd..f3759e0 100644
--- a/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsNotedCallback.aidl
@@ -18,5 +18,5 @@
 
 // Iterface to observe op note/checks of ops
 oneway interface IAppOpsNotedCallback {
-    void opNoted(int op, int uid, String packageName, int flags, int mode);
+    void opNoted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
 }
diff --git a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
index b0cb2a8..3a108e7 100644
--- a/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsStartedCallback.aidl
@@ -18,5 +18,5 @@
 
 // Iterface to observe op starts
 oneway interface IAppOpsStartedCallback {
-    void opStarted(int op, int uid, String packageName, int flags, int mode);
+    void opStarted(int op, int uid, String packageName, String attributionTag, int flags, int mode);
 }
diff --git a/core/java/com/android/internal/listeners/ListenerTransportManager.java b/core/java/com/android/internal/listeners/ListenerTransportManager.java
index 0d5d1b7b..d5b5619 100644
--- a/core/java/com/android/internal/listeners/ListenerTransportManager.java
+++ b/core/java/com/android/internal/listeners/ListenerTransportManager.java
@@ -17,6 +17,7 @@
 package com.android.internal.listeners;
 
 import android.os.RemoteException;
+import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -36,13 +37,17 @@
     @GuardedBy("mRegistrations")
     private final Map<Object, WeakReference<TTransport>> mRegistrations;
 
-    protected ListenerTransportManager() {
+    protected ListenerTransportManager(boolean allowServerSideTransportRemoval) {
         // using weakhashmap means that the transport may be GCed if the server drops its reference,
         // and thus the listener may be GCed as well if the client drops that reference. if the
         // server will never drop a reference without warning (ie, transport removal may only be
         // initiated from the client side), then arraymap or similar may be used without fear of
         // memory leaks.
-        mRegistrations = new WeakHashMap<>();
+        if (allowServerSideTransportRemoval) {
+            mRegistrations = new WeakHashMap<>();
+        } else {
+            mRegistrations = new ArrayMap<>();
+        }
     }
 
     /**
@@ -53,16 +58,21 @@
             synchronized (mRegistrations) {
                 // ordering of operations is important so that if an error occurs at any point we
                 // are left in a reasonable state
-                registerTransport(transport);
-                WeakReference<TTransport> oldTransportRef = mRegistrations.put(key,
-                        new WeakReference<>(transport));
+                TTransport oldTransport;
+                WeakReference<TTransport> oldTransportRef = mRegistrations.get(key);
                 if (oldTransportRef != null) {
-                    TTransport oldTransport = oldTransportRef.get();
-                    if (oldTransport != null) {
-                        oldTransport.unregister();
-                        unregisterTransport(oldTransport);
-                    }
+                    oldTransport = oldTransportRef.get();
+                } else {
+                    oldTransport = null;
                 }
+
+                if (oldTransport == null) {
+                    registerTransport(transport);
+                } else {
+                    registerTransport(transport, oldTransport);
+                    oldTransport.unregister();
+                }
+                mRegistrations.put(key, new WeakReference<>(transport));
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -91,7 +101,33 @@
         }
     }
 
+    /**
+     * Registers a new transport.
+     */
     protected abstract void registerTransport(TTransport transport) throws RemoteException;
 
+    /**
+     * Registers a new transport that is replacing the given old transport. Implementations must
+     * ensure that if they throw a remote exception, the call does not have any side effects.
+     */
+    protected void registerTransport(TTransport transport, TTransport oldTransport)
+            throws RemoteException {
+        registerTransport(transport);
+        try {
+            unregisterTransport(oldTransport);
+        } catch (RemoteException e) {
+            try {
+                // best effort to ensure there are no side effects
+                unregisterTransport(transport);
+            } catch (RemoteException suppressed) {
+                e.addSuppressed(suppressed);
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Unregisters an existing transport.
+     */
     protected abstract void unregisterTransport(TTransport transport) throws RemoteException;
 }
diff --git a/core/java/com/android/internal/os/KernelCpuBpfTracking.java b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
index 2852547..387d327 100644
--- a/core/java/com/android/internal/os/KernelCpuBpfTracking.java
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -16,11 +16,79 @@
 
 package com.android.internal.os;
 
-/** CPU tracking using eBPF. */
+import android.annotation.Nullable;
+
+/**
+ * CPU tracking using eBPF.
+ *
+ * The tracking state and data about available frequencies are cached to avoid JNI calls and
+ * creating temporary arrays. The data is stored in a format that is convenient for metrics
+ * computation.
+ *
+ * Synchronization is not needed because the underlying native library can be invoked concurrently
+ * and getters are idempotent.
+ */
 public final class KernelCpuBpfTracking {
+    private static boolean sTracking = false;
+
+    /** Cached mapping from frequency index to frequency in kHz. */
+    private static long[] sFreqs = null;
+
+    /** Cached mapping from frequency index to CPU cluster / policy. */
+    private static int[] sFreqsClusters = null;
+
     private KernelCpuBpfTracking() {
     }
 
     /** Returns whether CPU tracking using eBPF is supported. */
     public static native boolean isSupported();
+
+    /** Starts CPU tracking using eBPF. */
+    public static boolean startTracking() {
+        if (!sTracking) {
+            sTracking = startTrackingInternal();
+        }
+        return sTracking;
+    }
+
+    private static native boolean startTrackingInternal();
+
+    /** Returns frequencies in kHz on which CPU is tracked. Empty if not supported. */
+    public static long[] getFreqs() {
+        if (sFreqs == null) {
+            long[] freqs = getFreqsInternal();
+            if (freqs == null) {
+                return new long[0];
+            }
+            sFreqs = freqs;
+        }
+        return sFreqs;
+    }
+
+    @Nullable
+    static native long[] getFreqsInternal();
+
+    /**
+     * Returns the cluster (policy) number  for each frequency on which CPU is tracked. Empty if
+     * not supported.
+     */
+    public static int[] getFreqsClusters() {
+        if (sFreqsClusters == null) {
+            int[] freqsClusters = getFreqsClustersInternal();
+            if (freqsClusters == null) {
+                return new int[0];
+            }
+            sFreqsClusters = freqsClusters;
+        }
+        return sFreqsClusters;
+    }
+
+    @Nullable
+    private static native int[] getFreqsClustersInternal();
+
+    /** Returns the number of clusters (policies). */
+    public static int getClusters() {
+        int[] freqClusters = getFreqsClusters();
+        return freqClusters.length > 0 ? freqClusters[freqClusters.length - 1] + 1 : 0;
+    }
 }
diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
index 06760e1..553eda4 100644
--- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
+++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
@@ -16,22 +16,22 @@
 
 package com.android.internal.os;
 
-/**
- * Reads total CPU time bpf map.
- */
+import android.annotation.Nullable;
+
+/** Reads total CPU time bpf map. */
 public final class KernelCpuTotalBpfMapReader {
     private KernelCpuTotalBpfMapReader() {
     }
 
-    /** Reads total CPU time from bpf map. */
-    public static native boolean read(Callback callback);
-
-    /** Callback accepting values read from bpf map. */
-    public interface Callback {
-        /**
-         * Accepts values read from bpf map: cluster index, frequency in kilohertz and time in
-         * milliseconds that the cpu cluster spent at the frequency (excluding sleep).
-         */
-        void accept(int cluster, int freqKhz, long timeMs);
+    /** Reads total CPU times (excluding sleep) per frequency in milliseconds from bpf map. */
+    @Nullable
+    public static long[] read() {
+        if (!KernelCpuBpfTracking.startTracking()) {
+            return null;
+        }
+        return readInternal();
     }
+
+    @Nullable
+    private static native long[] readInternal();
 }
diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
index dafb924..52c0c3f 100644
--- a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -68,14 +68,15 @@
 
     final String mTag = this.getClass().getSimpleName();
     private int mErrors = 0;
-    private boolean mTracking = false;
     protected SparseArray<long[]> mData = new SparseArray<>();
     private long mLastReadTime = 0;
     protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
     protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
     protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
 
-    public native boolean startTrackingBpfTimes();
+    public boolean startTrackingBpfTimes() {
+        return KernelCpuBpfTracking.startTracking();
+    }
 
     protected abstract boolean readBpfData();
 
@@ -116,7 +117,7 @@
         if (mErrors > ERROR_THRESHOLD) {
             return null;
         }
-        if (!mTracking && !startTrackingBpfTimes()) {
+        if (!startTrackingBpfTimes()) {
             Slog.w(mTag, "Failed to start tracking");
             mErrors++;
             return null;
@@ -182,7 +183,9 @@
         protected final native boolean readBpfData();
 
         @Override
-        public final native long[] getDataDimensions();
+        public final long[] getDataDimensions() {
+            return KernelCpuBpfTracking.getFreqsInternal();
+        }
 
         @Override
         public void removeUidsInRange(int startUid, int endUid) {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 5b327d4..d0504fb 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -421,11 +421,18 @@
 };
 
 static jlong nativeInitSensorEventQueue(JNIEnv *env, jclass clazz, jlong sensorManager,
-        jobject eventQWeak, jobject msgQ, jstring packageName, jint mode) {
+                                        jobject eventQWeak, jobject msgQ, jstring packageName,
+                                        jint mode, jstring opPackageName, jstring attributionTag) {
     SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
     ScopedUtfChars packageUtf(env, packageName);
     String8 clientName(packageUtf.c_str());
-    sp<SensorEventQueue> queue(mgr->createEventQueue(clientName, mode));
+
+    String16 attributionTagName("");
+    if (attributionTag != nullptr) {
+        ScopedUtfChars attrUtf(env, attributionTag);
+        attributionTagName = String16(attrUtf.c_str());
+    }
+    sp<SensorEventQueue> queue(mgr->createEventQueue(clientName, mode, attributionTagName));
 
     if (queue == NULL) {
         jniThrowRuntimeException(env, "Cannot construct native SensorEventQueue.");
@@ -517,29 +524,20 @@
 };
 
 static const JNINativeMethod gBaseEventQueueMethods[] = {
-    {"nativeInitBaseEventQueue",
-             "(JLjava/lang/ref/WeakReference;Landroid/os/MessageQueue;Ljava/lang/String;ILjava/lang/String;)J",
-             (void*)nativeInitSensorEventQueue },
+        {"nativeInitBaseEventQueue",
+         "(JLjava/lang/ref/WeakReference;Landroid/os/MessageQueue;Ljava/lang/String;ILjava/lang/"
+         "String;Ljava/lang/String;)J",
+         (void *)nativeInitSensorEventQueue},
 
-    {"nativeEnableSensor",
-            "(JIII)I",
-            (void*)nativeEnableSensor },
+        {"nativeEnableSensor", "(JIII)I", (void *)nativeEnableSensor},
 
-    {"nativeDisableSensor",
-            "(JI)I",
-            (void*)nativeDisableSensor },
+        {"nativeDisableSensor", "(JI)I", (void *)nativeDisableSensor},
 
-    {"nativeDestroySensorEventQueue",
-            "(J)V",
-            (void*)nativeDestroySensorEventQueue },
+        {"nativeDestroySensorEventQueue", "(J)V", (void *)nativeDestroySensorEventQueue},
 
-    {"nativeFlushSensor",
-            "(J)I",
-            (void*)nativeFlushSensor },
+        {"nativeFlushSensor", "(J)I", (void *)nativeFlushSensor},
 
-    {"nativeInjectSensorData",
-            "(JI[FIJ)I",
-            (void*)nativeInjectSensorData },
+        {"nativeInjectSensorData", "(JI[FIJ)I", (void *)nativeInjectSensorData},
 };
 
 } //unnamed namespace
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 065c79b..da60a75 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1419,6 +1419,28 @@
     return nativeToJavaStatus(status);
 }
 
+static void android_media_AudioTrack_setLogSessionId(JNIEnv *env, jobject thiz,
+                                                     jstring jlogSessionId) {
+    sp<AudioTrack> track = getAudioTrack(env, thiz);
+    if (track == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Unable to retrieve AudioTrack pointer for setLogSessionId()");
+    }
+    ScopedUtfChars logSessionId(env, jlogSessionId);
+    ALOGV("%s: logSessionId %s", __func__, logSessionId.c_str());
+    track->setLogSessionId(logSessionId.c_str());
+}
+
+static void android_media_AudioTrack_setPlayerIId(JNIEnv *env, jobject thiz, jint playerIId) {
+    sp<AudioTrack> track = getAudioTrack(env, thiz);
+    if (track == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Unable to retrieve AudioTrack pointer for setPlayerIId()");
+    }
+    ALOGV("%s: playerIId %d", __func__, playerIId);
+    track->setPlayerIId(playerIId);
+}
+
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
@@ -1496,6 +1518,9 @@
          (void *)android_media_AudioTrack_getAudioDescriptionMixLeveldB},
         {"native_set_dual_mono_mode", "(I)I", (void *)android_media_AudioTrack_setDualMonoMode},
         {"native_get_dual_mono_mode", "([I)I", (void *)android_media_AudioTrack_getDualMonoMode},
+        {"native_setLogSessionId", "(Ljava/lang/String;)V",
+         (void *)android_media_AudioTrack_setLogSessionId},
+        {"native_setPlayerIId", "(I)V", (void *)android_media_AudioTrack_setPlayerIId},
 };
 
 // field names found in android/media/AudioTrack.java
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 7a3366a..d11ee3a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
 #include <android-base/chrono_utils.h>
 #include <android/graphics/region.h>
 #include <android/gui/BnScreenCaptureListener.h>
+#include <android/hardware/display/IDeviceProductInfoConstants.h>
 #include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -44,14 +45,15 @@
 #include <ui/BlurRegion.h>
 #include <ui/ConfigStoreTypes.h>
 #include <ui/DeviceProductInfo.h>
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayMode.h>
 #include <ui/DisplayedFrameStats.h>
+#include <ui/DynamicDisplayInfo.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicTypes.h>
 #include <ui/HdrCapabilities.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/StaticDisplayInfo.h>
 #include <utils/LightRefBase.h>
 #include <utils/Log.h>
 
@@ -86,11 +88,22 @@
     jfieldID density;
     jfieldID secure;
     jfieldID deviceProductInfo;
-} gDisplayInfoClassInfo;
+} gStaticDisplayInfoClassInfo;
 
 static struct {
     jclass clazz;
     jmethodID ctor;
+    jfieldID supportedDisplayModes;
+    jfieldID activeDisplayModeId;
+    jfieldID supportedColorModes;
+    jfieldID activeColorMode;
+    jfieldID hdrCapabilities;
+} gDynamicDisplayInfoClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID id;
     jfieldID width;
     jfieldID height;
     jfieldID xDpi;
@@ -580,6 +593,15 @@
     transaction->setBlurRegions(ctrl, blurRegionVector);
 }
 
+static void nativeSetStretchEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                   jlong nativeObject, jfloat left, jfloat top, jfloat right,
+                                   jfloat bottom, jfloat vecX, jfloat vecY,
+                                   jfloat maxStretchAmount) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setStretchEffect(ctrl, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+}
+
 static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint w, jint h) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -999,63 +1021,120 @@
     } else {
         LOG_FATAL("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate");
     }
-    auto relativeAddress = env->NewIntArray(info->relativeAddress.size());
-    auto relativeAddressData = env->GetIntArrayElements(relativeAddress, nullptr);
-    for (int i = 0; i < info->relativeAddress.size(); i++) {
-        relativeAddressData[i] = info->relativeAddress[i];
+    jint connectionToSinkType;
+    // Relative address maps to HDMI physical address. All addresses are 4 digits long allowing
+    // for a 5–device-deep hierarchy. For more information, refer:
+    // Section 8.7 - Physical Address of HDMI Specification Version 1.3a
+    using android::hardware::display::IDeviceProductInfoConstants;
+    if (info->relativeAddress.size() != 4) {
+        connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_UNKNOWN;
+    } else if (info->relativeAddress[0] == 0) {
+        connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_BUILT_IN;
+    } else if (info->relativeAddress[1] == 0) {
+        connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_DIRECT;
+    } else {
+        connectionToSinkType = IDeviceProductInfoConstants::CONNECTION_TO_SINK_TRANSITIVE;
     }
-    env->ReleaseIntArrayElements(relativeAddress, relativeAddressData, 0);
 
     return env->NewObject(gDeviceProductInfoClassInfo.clazz, gDeviceProductInfoClassInfo.ctor, name,
                           manufacturerPnpId, productId, modelYear, manufactureDate,
-                          relativeAddress);
+                          connectionToSinkType);
 }
 
-static jobject nativeGetDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
-    DisplayInfo info;
+static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+    ui::StaticDisplayInfo info;
     if (const auto token = ibinderForJavaObject(env, tokenObj);
-        !token || SurfaceComposerClient::getDisplayInfo(token, &info) != NO_ERROR) {
+        !token || SurfaceComposerClient::getStaticDisplayInfo(token, &info) != NO_ERROR) {
         return nullptr;
     }
 
-    jobject object = env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor);
-    env->SetBooleanField(object, gDisplayInfoClassInfo.isInternal,
-                         info.connectionType == DisplayConnectionType::Internal);
-    env->SetFloatField(object, gDisplayInfoClassInfo.density, info.density);
-    env->SetBooleanField(object, gDisplayInfoClassInfo.secure, info.secure);
-    env->SetObjectField(object, gDisplayInfoClassInfo.deviceProductInfo,
+    jobject object =
+            env->NewObject(gStaticDisplayInfoClassInfo.clazz, gStaticDisplayInfoClassInfo.ctor);
+    env->SetBooleanField(object, gStaticDisplayInfoClassInfo.isInternal,
+                         info.connectionType == ui::DisplayConnectionType::Internal);
+    env->SetFloatField(object, gStaticDisplayInfoClassInfo.density, info.density);
+    env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure);
+    env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo,
                         convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo));
     return object;
 }
 
-static jobjectArray nativeGetDisplayModes(JNIEnv* env, jclass clazz, jobject tokenObj) {
-    Vector<ui::DisplayMode> modes;
-    if (const auto token = ibinderForJavaObject(env, tokenObj); !token ||
-        SurfaceComposerClient::getDisplayModes(token, &modes) != NO_ERROR || modes.isEmpty()) {
+static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode& config) {
+    jobject object = env->NewObject(gDisplayModeClassInfo.clazz, gDisplayModeClassInfo.ctor);
+    env->SetIntField(object, gDisplayModeClassInfo.id, config.id);
+    env->SetIntField(object, gDisplayModeClassInfo.width, config.resolution.getWidth());
+    env->SetIntField(object, gDisplayModeClassInfo.height, config.resolution.getHeight());
+    env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi);
+    env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
+
+    env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+    env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
+    env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
+                      config.presentationDeadline);
+    env->SetIntField(object, gDisplayModeClassInfo.group, config.group);
+    return object;
+}
+
+jobject convertDeviceProductInfoToJavaObject(JNIEnv* env, const HdrCapabilities& capabilities) {
+    const auto& types = capabilities.getSupportedHdrTypes();
+    std::vector<int32_t> intTypes;
+    for (auto type : types) {
+        intTypes.push_back(static_cast<int32_t>(type));
+    }
+    auto typesArray = env->NewIntArray(types.size());
+    env->SetIntArrayRegion(typesArray, 0, intTypes.size(), intTypes.data());
+
+    return env->NewObject(gHdrCapabilitiesClassInfo.clazz, gHdrCapabilitiesClassInfo.ctor,
+                          typesArray, capabilities.getDesiredMaxLuminance(),
+                          capabilities.getDesiredMaxAverageLuminance(),
+                          capabilities.getDesiredMinLuminance());
+}
+
+static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+    ui::DynamicDisplayInfo info;
+    if (const auto token = ibinderForJavaObject(env, tokenObj);
+        !token || SurfaceComposerClient::getDynamicDisplayInfo(token, &info) != NO_ERROR) {
         return nullptr;
     }
 
-    jobjectArray modesArray =
-            env->NewObjectArray(modes.size(), gDisplayModeClassInfo.clazz, nullptr);
-
-    for (size_t c = 0; c < modes.size(); ++c) {
-        const ui::DisplayMode& mode = modes[c];
-        jobject object = env->NewObject(gDisplayModeClassInfo.clazz, gDisplayModeClassInfo.ctor);
-        env->SetIntField(object, gDisplayModeClassInfo.width, mode.resolution.getWidth());
-        env->SetIntField(object, gDisplayModeClassInfo.height, mode.resolution.getHeight());
-        env->SetFloatField(object, gDisplayModeClassInfo.xDpi, mode.xDpi);
-        env->SetFloatField(object, gDisplayModeClassInfo.yDpi, mode.yDpi);
-
-        env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, mode.refreshRate);
-        env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, mode.appVsyncOffset);
-        env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
-                          mode.presentationDeadline);
-        env->SetIntField(object, gDisplayModeClassInfo.group, mode.group);
-        env->SetObjectArrayElement(modesArray, static_cast<jsize>(c), object);
-        env->DeleteLocalRef(object);
+    jobject object =
+            env->NewObject(gDynamicDisplayInfoClassInfo.clazz, gDynamicDisplayInfoClassInfo.ctor);
+    if (object == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
     }
 
-    return modesArray;
+    const auto numModes = info.supportedDisplayModes.size();
+    jobjectArray modesArray = env->NewObjectArray(numModes, gDisplayModeClassInfo.clazz, nullptr);
+    for (size_t i = 0; i < numModes; i++) {
+        const ui::DisplayMode& mode = info.supportedDisplayModes[i];
+        jobject displayModeObj = convertDisplayModeToJavaObject(env, mode);
+        env->SetObjectArrayElement(modesArray, static_cast<jsize>(i), displayModeObj);
+        env->DeleteLocalRef(displayModeObj);
+    }
+    env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
+    env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
+                     info.activeDisplayModeId);
+
+    jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
+    if (colorModesArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+    jint* colorModesArrayValues = env->GetIntArrayElements(colorModesArray, 0);
+    for (size_t i = 0; i < info.supportedColorModes.size(); i++) {
+        colorModesArrayValues[i] = static_cast<jint>(info.supportedColorModes[i]);
+    }
+    env->ReleaseIntArrayElements(colorModesArray, colorModesArrayValues, 0);
+    env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedColorModes, colorModesArray);
+
+    env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeColorMode,
+                     static_cast<jint>(info.activeColorMode));
+
+    env->SetObjectField(object, gDynamicDisplayInfoClassInfo.hdrCapabilities,
+                        convertDeviceProductInfoToJavaObject(env, info.hdrCapabilities));
+
+    return object;
 }
 
 static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
@@ -1063,9 +1142,8 @@
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return JNI_FALSE;
 
-    size_t defaultMode =
-            static_cast<size_t>(env->GetIntField(DesiredDisplayModeSpecs,
-                                                 gDesiredDisplayModeSpecsClassInfo.defaultMode));
+    ui::DisplayModeId defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
+                                                     gDesiredDisplayModeSpecsClassInfo.defaultMode);
     jboolean allowGroupSwitching =
             env->GetBooleanField(DesiredDisplayModeSpecs,
                                  gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching);
@@ -1095,7 +1173,7 @@
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return nullptr;
 
-    size_t defaultMode;
+    ui::DisplayModeId defaultMode;
     bool allowGroupSwitching;
     float primaryRefreshRateMin;
     float primaryRefreshRateMax;
@@ -1115,34 +1193,6 @@
                           appRequestRefreshRateMax);
 }
 
-static jint nativeGetActiveDisplayMode(JNIEnv* env, jclass clazz, jobject tokenObj) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
-    if (token == NULL) return -1;
-    return static_cast<jint>(SurfaceComposerClient::getActiveDisplayModeId(token));
-}
-
-static jintArray nativeGetDisplayColorModes(JNIEnv* env, jclass, jobject tokenObj) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
-    if (token == NULL) return NULL;
-    Vector<ui::ColorMode> colorModes;
-    if (SurfaceComposerClient::getDisplayColorModes(token, &colorModes) != NO_ERROR ||
-            colorModes.isEmpty()) {
-        return NULL;
-    }
-
-    jintArray colorModesArray = env->NewIntArray(colorModes.size());
-    if (colorModesArray == NULL) {
-        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
-        return NULL;
-    }
-    jint* colorModesArrayValues = env->GetIntArrayElements(colorModesArray, 0);
-    for (size_t i = 0; i < colorModes.size(); i++) {
-        colorModesArrayValues[i] = static_cast<jint>(colorModes[i]);
-    }
-    env->ReleaseIntArrayElements(colorModesArray, colorModesArrayValues, 0);
-    return colorModesArray;
-}
-
 static jobject nativeGetDisplayNativePrimaries(JNIEnv* env, jclass, jobject tokenObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return NULL;
@@ -1203,12 +1253,6 @@
     return jprimaries;
 }
 
-static jint nativeGetActiveColorMode(JNIEnv* env, jclass, jobject tokenObj) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
-    if (token == NULL) return -1;
-    return static_cast<jint>(SurfaceComposerClient::getActiveColorMode(token));
-}
-
 static jintArray nativeGetCompositionDataspaces(JNIEnv* env, jclass) {
     ui::Dataspace defaultDataspace, wcgDataspace;
     ui::PixelFormat defaultPixelFormat, wcgPixelFormat;
@@ -1414,26 +1458,6 @@
     transaction->reparent(ctrl, newParent);
 }
 
-static jobject nativeGetHdrCapabilities(JNIEnv* env, jclass clazz, jobject tokenObject) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
-    if (token == NULL) return NULL;
-
-    HdrCapabilities capabilities;
-    SurfaceComposerClient::getHdrCapabilities(token, &capabilities);
-
-    const auto& types = capabilities.getSupportedHdrTypes();
-    std::vector<int32_t> intTypes;
-    for (auto type : types) {
-        intTypes.push_back(static_cast<int32_t>(type));
-    }
-    auto typesArray = env->NewIntArray(types.size());
-    env->SetIntArrayRegion(typesArray, 0, intTypes.size(), intTypes.data());
-
-    return env->NewObject(gHdrCapabilitiesClassInfo.clazz, gHdrCapabilitiesClassInfo.ctor,
-            typesArray, capabilities.getDesiredMaxLuminance(),
-            capabilities.getDesiredMaxAverageLuminance(), capabilities.getDesiredMinLuminance());
-}
-
 static jboolean nativeGetAutoLowLatencyModeSupport(JNIEnv* env, jclass clazz, jobject tokenObject) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
     if (token == NULL) return NULL;
@@ -1754,6 +1778,8 @@
             (void*)nativeSetLayerStack },
     {"nativeSetBlurRegions", "(JJ[[FI)V",
             (void*)nativeSetBlurRegions },
+    {"nativeSetStretchEffect", "(JJFFFFFFF)V",
+            (void*) nativeSetStretchEffect },
     {"nativeSetShadowRadius", "(JJF)V",
             (void*)nativeSetShadowRadius },
     {"nativeSetFrameRate", "(JJFIZ)V",
@@ -1778,24 +1804,21 @@
             (void*)nativeSetDisplayProjection },
     {"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
             (void*)nativeSetDisplaySize },
-    {"nativeGetDisplayInfo", "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DisplayInfo;",
-            (void*)nativeGetDisplayInfo },
-    {"nativeGetDisplayModes", "(Landroid/os/IBinder;)[Landroid/view/SurfaceControl$DisplayMode;",
-            (void*)nativeGetDisplayModes },
-    {"nativeGetActiveDisplayMode", "(Landroid/os/IBinder;)I",
-            (void*)nativeGetActiveDisplayMode },
+    {"nativeGetStaticDisplayInfo",
+            "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$StaticDisplayInfo;",
+            (void*)nativeGetStaticDisplayInfo },
+    {"nativeGetDynamicDisplayInfo",
+            "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DynamicDisplayInfo;",
+            (void*)nativeGetDynamicDisplayInfo },
     {"nativeSetDesiredDisplayModeSpecs",
             "(Landroid/os/IBinder;Landroid/view/SurfaceControl$DesiredDisplayModeSpecs;)Z",
             (void*)nativeSetDesiredDisplayModeSpecs },
     {"nativeGetDesiredDisplayModeSpecs",
             "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DesiredDisplayModeSpecs;",
             (void*)nativeGetDesiredDisplayModeSpecs },
-    {"nativeGetDisplayColorModes", "(Landroid/os/IBinder;)[I",
-            (void*)nativeGetDisplayColorModes},
-    {"nativeGetDisplayNativePrimaries", "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DisplayPrimaries;",
+    {"nativeGetDisplayNativePrimaries",
+            "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DisplayPrimaries;",
             (void*)nativeGetDisplayNativePrimaries },
-    {"nativeGetActiveColorMode", "(Landroid/os/IBinder;)I",
-            (void*)nativeGetActiveColorMode},
     {"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z",
             (void*)nativeSetActiveColorMode},
     {"nativeGetAutoLowLatencyModeSupport", "(Landroid/os/IBinder;)Z",
@@ -1808,8 +1831,6 @@
             (void*)nativeSetGameContentType },
     {"nativeGetCompositionDataspaces", "()[I",
             (void*)nativeGetCompositionDataspaces},
-    {"nativeGetHdrCapabilities", "(Landroid/os/IBinder;)Landroid/view/Display$HdrCapabilities;",
-            (void*)nativeGetHdrCapabilities },
     {"nativeClearContentFrameStats", "(J)Z",
             (void*)nativeClearContentFrameStats },
     {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
@@ -1888,19 +1909,36 @@
     gIntegerClassInfo.clazz = MakeGlobalRefOrDie(env, integerClass);
     gIntegerClassInfo.ctor = GetMethodIDOrDie(env, gIntegerClassInfo.clazz, "<init>", "(I)V");
 
-    jclass infoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayInfo");
-    gDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, infoClazz);
-    gDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, infoClazz, "<init>", "()V");
-    gDisplayInfoClassInfo.isInternal = GetFieldIDOrDie(env, infoClazz, "isInternal", "Z");
-    gDisplayInfoClassInfo.density = GetFieldIDOrDie(env, infoClazz, "density", "F");
-    gDisplayInfoClassInfo.secure = GetFieldIDOrDie(env, infoClazz, "secure", "Z");
-    gDisplayInfoClassInfo.deviceProductInfo =
+    jclass infoClazz = FindClassOrDie(env, "android/view/SurfaceControl$StaticDisplayInfo");
+    gStaticDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, infoClazz);
+    gStaticDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, infoClazz, "<init>", "()V");
+    gStaticDisplayInfoClassInfo.isInternal = GetFieldIDOrDie(env, infoClazz, "isInternal", "Z");
+    gStaticDisplayInfoClassInfo.density = GetFieldIDOrDie(env, infoClazz, "density", "F");
+    gStaticDisplayInfoClassInfo.secure = GetFieldIDOrDie(env, infoClazz, "secure", "Z");
+    gStaticDisplayInfoClassInfo.deviceProductInfo =
             GetFieldIDOrDie(env, infoClazz, "deviceProductInfo",
                             "Landroid/hardware/display/DeviceProductInfo;");
 
+    jclass dynamicInfoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DynamicDisplayInfo");
+    gDynamicDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, dynamicInfoClazz);
+    gDynamicDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, dynamicInfoClazz, "<init>", "()V");
+    gDynamicDisplayInfoClassInfo.supportedDisplayModes =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "supportedDisplayModes",
+                            "[Landroid/view/SurfaceControl$DisplayMode;");
+    gDynamicDisplayInfoClassInfo.activeDisplayModeId =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+    gDynamicDisplayInfoClassInfo.supportedColorModes =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
+    gDynamicDisplayInfoClassInfo.activeColorMode =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "activeColorMode", "I");
+    gDynamicDisplayInfoClassInfo.hdrCapabilities =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "hdrCapabilities",
+                            "Landroid/view/Display$HdrCapabilities;");
+
     jclass modeClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayMode");
     gDisplayModeClassInfo.clazz = MakeGlobalRefOrDie(env, modeClazz);
     gDisplayModeClassInfo.ctor = GetMethodIDOrDie(env, modeClazz, "<init>", "()V");
+    gDisplayModeClassInfo.id = GetFieldIDOrDie(env, modeClazz, "id", "I");
     gDisplayModeClassInfo.width = GetFieldIDOrDie(env, modeClazz, "width", "I");
     gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I");
     gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
@@ -1948,7 +1986,7 @@
                              "Ljava/lang/String;"
                              "Ljava/lang/Integer;"
                              "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;"
-                             "[I)V");
+                             "I)V");
 
     jclass deviceProductInfoManufactureDateClazz =
             FindClassOrDie(env, "android/hardware/display/DeviceProductInfo$ManufactureDate");
diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
index e6a82f6..6b41b2e 100644
--- a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
@@ -24,8 +24,48 @@
     return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean KernelCpuBpfTracking_startTrackingInternal(JNIEnv *, jobject) {
+    return android::bpf::startTrackingUidTimes();
+}
+
+static jlongArray KernelCpuBpfTracking_getFreqsInternal(JNIEnv *env, jobject) {
+    auto freqs = android::bpf::getCpuFreqs();
+    if (!freqs) return NULL;
+
+    std::vector<uint64_t> allFreqs;
+    for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
+
+    auto ar = env->NewLongArray(allFreqs.size());
+    if (ar != NULL) {
+        env->SetLongArrayRegion(ar, 0, allFreqs.size(),
+                                reinterpret_cast<const jlong *>(allFreqs.data()));
+    }
+    return ar;
+}
+
+static jintArray KernelCpuBpfTracking_getFreqsClustersInternal(JNIEnv *env, jobject) {
+    auto freqs = android::bpf::getCpuFreqs();
+    if (!freqs) return NULL;
+
+    std::vector<uint32_t> freqsClusters;
+    uint32_t clusters = freqs->size();
+    for (uint32_t c = 0; c < clusters; ++c) {
+        freqsClusters.insert(freqsClusters.end(), (*freqs)[c].size(), c);
+    }
+
+    auto ar = env->NewIntArray(freqsClusters.size());
+    if (ar != NULL) {
+        env->SetIntArrayRegion(ar, 0, freqsClusters.size(),
+                               reinterpret_cast<const jint *>(freqsClusters.data()));
+    }
+    return ar;
+}
+
 static const JNINativeMethod methods[] = {
         {"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported},
+        {"startTrackingInternal", "()Z", (void *)KernelCpuBpfTracking_startTrackingInternal},
+        {"getFreqsInternal", "()[J", (void *)KernelCpuBpfTracking_getFreqsInternal},
+        {"getFreqsClustersInternal", "()[I", (void *)KernelCpuBpfTracking_getFreqsClustersInternal},
 };
 
 int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
index 7249238..ad43014 100644
--- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
@@ -20,33 +20,27 @@
 
 namespace android {
 
-static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) {
-    jclass callbackClass = env->GetObjectClass(callback);
-    jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V");
-    if (callbackMethod == 0) {
-        return JNI_FALSE;
-    }
-
-    auto freqs = android::bpf::getCpuFreqs();
-    if (!freqs) return JNI_FALSE;
+static jlongArray KernelCpuTotalBpfMapReader_readInternal(JNIEnv *env, jobject) {
     auto freqTimes = android::bpf::getTotalCpuFreqTimes();
     if (!freqTimes) return JNI_FALSE;
 
-    auto freqsClusterSize = (*freqs).size();
-    for (uint32_t clusterIndex = 0; clusterIndex < freqsClusterSize; ++clusterIndex) {
-        auto freqsSize = (*freqs)[clusterIndex].size();
-        for (uint32_t freqIndex = 0; freqIndex < freqsSize; ++freqIndex) {
-            env->CallVoidMethod(callback, callbackMethod, clusterIndex,
-                                (*freqs)[clusterIndex][freqIndex],
-                                (*freqTimes)[clusterIndex][freqIndex] / 1000000);
+    std::vector<uint64_t> allTimes;
+    for (const auto &vec : *freqTimes) {
+        for (const auto &timeNs : vec) {
+            allTimes.push_back(timeNs / 1000000);
         }
     }
-    return JNI_TRUE;
+
+    auto ar = env->NewLongArray(allTimes.size());
+    if (ar != NULL) {
+        env->SetLongArrayRegion(ar, 0, allTimes.size(),
+                                reinterpret_cast<const jlong *>(allTimes.data()));
+    }
+    return ar;
 }
 
 static const JNINativeMethod methods[] = {
-        {"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z",
-         (void *)KernelCpuTotalBpfMapReader_read},
+        {"readInternal", "()[J", (void *)KernelCpuTotalBpfMapReader_readInternal},
 };
 
 int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
index 7c68de5..7900d30 100644
--- a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -82,25 +82,9 @@
     return true;
 }
 
-static jlongArray KernelCpuUidFreqTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
-    auto freqs = android::bpf::getCpuFreqs();
-    if (!freqs) return NULL;
-
-    std::vector<uint64_t> allFreqs;
-    for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
-
-    auto ar = env->NewLongArray(allFreqs.size());
-    if (ar != NULL) {
-        env->SetLongArrayRegion(ar, 0, allFreqs.size(),
-                                reinterpret_cast<const jlong *>(allFreqs.data()));
-    }
-    return ar;
-}
-
 static const JNINativeMethod gFreqTimeMethods[] = {
         {"removeUidRange", "(II)Z", (void *)KernelCpuUidFreqTimeBpfMapReader_removeUidRange},
         {"readBpfData", "()Z", (void *)KernelCpuUidFreqTimeBpfMapReader_readBpfData},
-        {"getDataDimensions", "()[J", (void *)KernelCpuUidFreqTimeBpfMapReader_getDataDimensions},
 };
 
 static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
@@ -186,10 +170,6 @@
         {"KernelCpuUidClusterTimeBpfMapReader", gClusterTimeMethods, NELEM(gClusterTimeMethods)},
 };
 
-static jboolean KernelCpuUidBpfMapReader_startTrackingBpfTimes(JNIEnv *, jobject) {
-    return android::bpf::startTrackingUidTimes();
-}
-
 int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
     gSparseArrayClassInfo.clazz = FindClassOrDie(env, "android/util/SparseArray");
     gSparseArrayClassInfo.clazz = MakeGlobalRefOrDie(env, gSparseArrayClassInfo.clazz);
@@ -198,14 +178,10 @@
     gSparseArrayClassInfo.get =
             GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "get", "(I)Ljava/lang/Object;");
     constexpr auto readerName = "com/android/internal/os/KernelCpuUidBpfMapReader";
-    constexpr JNINativeMethod method = {"startTrackingBpfTimes", "()Z",
-                                        (void *)KernelCpuUidBpfMapReader_startTrackingBpfTimes};
-
-    int ret = RegisterMethodsOrDie(env, readerName, &method, 1);
-    if (ret < 0) return ret;
     auto c = FindClassOrDie(env, readerName);
     gmData = GetFieldIDOrDie(env, c, "mData", "Landroid/util/SparseArray;");
 
+    int ret = 0;
     for (const auto &m : gAllMethods) {
         auto fullName = android::base::StringPrintf("%s$%s", readerName, m.name);
         ret = RegisterMethodsOrDie(env, fullName.c_str(), m.methods, m.numMethods);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a85996a..5dd8580 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3983,6 +3983,13 @@
     <permission android:name="com.android.permission.USE_INSTALLER_V2"
         android:protectionLevel="signature|verifier" />
 
+    <!-- Allows an application to use System Data Loaders.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi @TestApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
diff --git a/core/res/res/drawable/toast_frame.xml b/core/res/res/drawable/toast_frame.xml
index d57bd6a..44c00c0 100644
--- a/core/res/res/drawable/toast_frame.xml
+++ b/core/res/res/drawable/toast_frame.xml
@@ -17,8 +17,7 @@
 -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <!-- background is material_grey_200 with .9 alpha -->
-    <solid android:color="#E6EEEEEE" />
-    <corners android:radius="22dp" />
+    <solid android:color="?android:attr/colorBackground" />
+    <corners android:radius="28dp" />
 </shape>
 
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index db586ec..8fcb77f 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -18,24 +18,27 @@
 */
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="?android:attr/toastFrameBackground">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:maxWidth="@dimen/toast_width"
+    android:background="?android:attr/toastFrameBackground"
+    android:layout_marginEnd="16dp"
+    android:layout_marginStart="16dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
 
     <TextView
         android:id="@android:id/message"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:layout_marginHorizontal="24dp"
-        android:layout_marginVertical="15dp"
-        android:layout_gravity="center_horizontal"
-        android:textAppearance="@style/TextAppearance.Toast"
-        android:textColor="@color/primary_text_default_material_light"
-        />
-
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:lineHeight="20sp"
+        android:textAppearance="@style/TextAppearance.Toast"/>
 </LinearLayout>
-
-
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index 9e87a47..42c2c69 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -78,4 +78,5 @@
 
     <dimen name="chooser_preview_width">480dp</dimen>
 
+    <dimen name="toast_y_offset">24dp</dimen>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 3c712ed..1ca5498 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -30,7 +30,13 @@
          will be displayed in the app launcher and elsewhere. -->
     <dimen name="app_icon_size">48dip</dimen>
 
-    <dimen name="toast_y_offset">24dp</dimen>
+    <!--  Offset from the bottom of the device a toast shows -->
+    <dimen name="toast_y_offset">48dp</dimen>
+    <!-- Max width of a toast -->
+    <dimen name="toast_width">300dp</dimen>
+    <!-- Text size of the message within a toast -->
+    <dimen name="toast_text_size">14sp</dimen>
+
     <!-- Height of the status bar -->
     <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
     <!-- Height of the status bar in portrait. The height should be
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 24afe07..c7ded0c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -964,8 +964,9 @@
     </style>
 
     <style name="TextAppearance.Toast">
-        <item name="fontFamily">sans-serif</item>
+        <item name="fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="textSize">14sp</item>
+        <item name="textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.Tooltip">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b5af524..e73d6e3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4203,4 +4203,6 @@
   <java-symbol type="bool" name="config_telephony5gNonStandalone" />
 
   <java-symbol type="bool" name="config_voice_data_sms_auto_fallback" />
+
+  <java-symbol type="bool" name="config_enableOneHandedKeyguard" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 266ff3d..1c6b3cc 100644
--- a/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -33,17 +33,20 @@
 
     @Test
     public void testEquals() {
-        ExternalTimeSuggestion one = new ExternalTimeSuggestion(ARBITRARY_TIME);
+        ExternalTimeSuggestion one = new ExternalTimeSuggestion(
+                ARBITRARY_TIME.getReferenceTimeMillis(),
+                ARBITRARY_TIME.getValue());
         assertEquals(one, one);
 
-        ExternalTimeSuggestion two = new ExternalTimeSuggestion(ARBITRARY_TIME);
+        ExternalTimeSuggestion two = new ExternalTimeSuggestion(
+                ARBITRARY_TIME.getReferenceTimeMillis(),
+                ARBITRARY_TIME.getValue());
         assertEquals(one, two);
         assertEquals(two, one);
 
-        TimestampedValue<Long> differentTime = new TimestampedValue<>(
+        ExternalTimeSuggestion three = new ExternalTimeSuggestion(
                 ARBITRARY_TIME.getReferenceTimeMillis() + 1,
                 ARBITRARY_TIME.getValue());
-        ExternalTimeSuggestion three = new ExternalTimeSuggestion(differentTime);
         assertNotEquals(one, three);
         assertNotEquals(three, one);
 
@@ -55,7 +58,9 @@
 
     @Test
     public void testParcelable() {
-        ExternalTimeSuggestion suggestion = new ExternalTimeSuggestion(ARBITRARY_TIME);
+        ExternalTimeSuggestion suggestion = new ExternalTimeSuggestion(
+                ARBITRARY_TIME.getReferenceTimeMillis(),
+                ARBITRARY_TIME.getValue());
         assertRoundTripParcelable(suggestion);
 
         // DebugInfo should also be stored (but is not checked by equals())
diff --git a/data/etc/car/com.android.car.bugreport.xml b/data/etc/car/com.android.car.bugreport.xml
index 432a838..c3642d88 100644
--- a/data/etc/car/com.android.car.bugreport.xml
+++ b/data/etc/car/com.android.car.bugreport.xml
@@ -20,5 +20,6 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/>
     </privapp-permissions>
   </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 5633de3..cabfad4 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1321,6 +1321,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-567946587": {
+      "message": "Requested redraw for orientation change: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-561092364": {
       "message": "onPointerDownOutsideFocusLocked called on %s",
       "level": "INFO",
diff --git a/graphics/java/android/graphics/BlurShader.java b/graphics/java/android/graphics/BlurShader.java
deleted file mode 100644
index 2e4bd7d..0000000
--- a/graphics/java/android/graphics/BlurShader.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-/**
- * A subclass of shader that blurs input from another {@link android.graphics.Shader} instance
- * or all the drawing commands with the {@link android.graphics.Paint} that this shader is
- * attached to.
- */
-public final class BlurShader extends Shader {
-
-    private final float mRadiusX;
-    private final float mRadiusY;
-    private final Shader mInputShader;
-    private final TileMode mEdgeTreatment;
-
-    private long mNativeInputShader = 0;
-
-    /**
-     * Create a {@link BlurShader} that blurs the contents of the optional input shader
-     * with the specified radius along the x and y axis. If no input shader is provided
-     * then all drawing commands issued with a {@link android.graphics.Paint} that this
-     * shader is installed in will be blurred.
-     *
-     * This uses a default {@link TileMode#DECAL} for edge treatment
-     *
-     * @param radiusX Radius of blur along the X axis
-     * @param radiusY Radius of blur along the Y axis
-     * @param inputShader Input shader that provides the content to be blurred
-     */
-    public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader) {
-        this(radiusX, radiusY, inputShader, TileMode.DECAL);
-    }
-
-    /**
-     * Create a {@link BlurShader} that blurs the contents of the optional input shader
-     * with the specified radius along the x and y axis. If no input shader is provided
-     * then all drawing commands issued with a {@link android.graphics.Paint} that this
-     * shader is installed in will be blurred
-     * @param radiusX Radius of blur along the X axis
-     * @param radiusY Radius of blur along the Y axis
-     * @param inputShader Input shader that provides the content to be blurred
-     * @param edgeTreatment Policy for how to blur content near edges of the blur shader
-     */
-    public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader,
-            @NonNull TileMode edgeTreatment) {
-        mRadiusX = radiusX;
-        mRadiusY = radiusY;
-        mInputShader = inputShader;
-        mEdgeTreatment = edgeTreatment;
-    }
-
-    /** @hide **/
-    @Override
-    protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
-        mNativeInputShader = mInputShader != null
-                ? mInputShader.getNativeInstance(filterFromPaint) : 0;
-        return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader,
-                mEdgeTreatment.nativeInt);
-    }
-
-    /** @hide **/
-    @Override
-    protected boolean shouldDiscardNativeInstance(boolean filterFromPaint) {
-        long currentNativeInstance = mInputShader != null
-                ? mInputShader.getNativeInstance(filterFromPaint) : 0;
-        return mNativeInputShader != currentNativeInstance;
-    }
-
-    private static native long nativeCreate(long nativeMatrix, float radiusX, float radiusY,
-            long inputShader, int edgeTreatment);
-}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 4a92cf1..f6f770b 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -272,6 +272,16 @@
         void positionChanged(long frameNumber, int left, int top, int right, int bottom);
 
         /**
+         * Call to apply a stretch effect to any child SurfaceControl layers
+         *
+         * TODO: Fold this into positionChanged & have HWUI do the ASurfaceControl calls?
+         *
+         * @hide
+         */
+        default void applyStretch(long frameNumber, float left, float top, float right,
+                float bottom, float vecX, float vecY, float maxStretch) { }
+
+        /**
          * Called by native on RenderThread to notify that the view is no longer in the
          * draw tree. UI thread is blocked at this point.
          *
@@ -312,6 +322,14 @@
                 pul.positionLost(frameNumber);
             }
         }
+
+        @Override
+        public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
+                float vecX, float vecY, float maxStretch) {
+            for (PositionUpdateListener pul : mListeners) {
+                pul.applyStretch(frameNumber, left, top, right, bottom, vecX, vecY, maxStretch);
+            }
+        }
     }
 
     /**
@@ -707,7 +725,7 @@
         if (1.0 < vecY || vecY < -1.0) {
             throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
         }
-        if (top <= bottom || right <= left) {
+        if (top >= bottom || left >= right) {
             throw new IllegalArgumentException(
                     "Stretch region must not be empty, got "
                             + new RectF(left, top, right, bottom).toString());
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
index 347c2b4..0dea87c 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
@@ -14,35 +14,52 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.wm.shell.sizecompatui.SizeCompatHintPopup
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="@android:color/background_light"
-    android:orientation="vertical">
+    android:layout_height="wrap_content">
 
-    <TextView
-        android:layout_width="180dp"
-        android:layout_height="wrap_content"
-        android:paddingLeft="10dp"
-        android:paddingRight="10dp"
-        android:paddingTop="10dp"
-        android:text="@string/restart_button_description"
-        android:textAlignment="viewStart"
-        android:textColor="@android:color/primary_text_light"
-        android:textSize="16sp" />
-
-    <Button
-        android:id="@+id/got_it"
+    <FrameLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:includeFontPadding="false"
-        android:layout_gravity="end"
-        android:minHeight="36dp"
-        android:background="?android:attr/selectableItemBackground"
-        android:text="@string/got_it"
-        android:textAllCaps="true"
-        android:textColor="#3c78d8"
-        android:textSize="16sp"
-        android:textStyle="bold" />
+        android:gravity="center"
+        android:clipToPadding="false"
+        android:padding="@dimen/bubble_elevation">
 
-</LinearLayout>
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@android:color/background_light"
+            android:elevation="@dimen/bubble_elevation"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="180dp"
+                android:layout_height="wrap_content"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp"
+                android:paddingTop="10dp"
+                android:text="@string/restart_button_description"
+                android:textAlignment="viewStart"
+                android:textColor="@android:color/primary_text_light"
+                android:textSize="16sp"/>
+
+            <Button
+                android:id="@+id/got_it"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:includeFontPadding="false"
+                android:layout_gravity="end"
+                android:minHeight="36dp"
+                android:background="?android:attr/selectableItemBackground"
+                android:text="@string/got_it"
+                android:textAllCaps="true"
+                android:textColor="#3c78d8"
+                android:textSize="16sp"
+                android:textStyle="bold"/>
+
+        </LinearLayout>
+
+    </FrameLayout>
+
+</com.android.wm.shell.sizecompatui.SizeCompatHintPopup>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index f8b4dd9..a0a76d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -98,7 +98,16 @@
         PICTURE_IN_PICTURE_CHANGE_ASPECT_RATIO(609),
 
         @UiEvent(doc = "User resize of the picture-in-picture window")
-        PICTURE_IN_PICTURE_RESIZE(610);
+        PICTURE_IN_PICTURE_RESIZE(610),
+
+        @UiEvent(doc = "User unstashed picture-in-picture")
+        PICTURE_IN_PICTURE_STASH_UNSTASHED(709),
+
+        @UiEvent(doc = "User stashed picture-in-picture to the left side")
+        PICTURE_IN_PICTURE_STASH_LEFT(710),
+
+        @UiEvent(doc = "User stashed picture-in-picture to the right side")
+        PICTURE_IN_PICTURE_STASH_RIGHT(711);
 
         private final int mId;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index afc7b52..5e23281 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -170,6 +170,7 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mMenuController = menuController;
+        mPipUiEventLogger = pipUiEventLogger;
         mMenuController.addListener(new PipMenuListener());
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
@@ -186,6 +187,8 @@
                 () -> {
                     if (mPipBoundsState.isStashed()) {
                         animateToUnStashedState();
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
                         mPipBoundsState.setStashed(STASH_TYPE_NONE);
                     } else {
                         mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
@@ -206,8 +209,6 @@
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
                 this::onAccessibilityShowMenu, this::updateMovementBounds, mainExecutor);
 
-        mPipUiEventLogger = pipUiEventLogger;
-
         mEnableStash = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_STASHING,
@@ -867,6 +868,8 @@
                 if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
                     mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
                 } else {
+                    mPipUiEventLogger.log(
+                            PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
                     mPipBoundsState.setStashed(STASH_TYPE_NONE);
                     mMotionHelper.flingToSnapTarget(vel.x, vel.y,
                             this::flingEndAction /* endAction */);
@@ -897,6 +900,8 @@
                 if (!mTouchState.isWaitingForDoubleTap()) {
                     if (mPipBoundsState.isStashed()) {
                         animateToUnStashedState();
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
                         mPipBoundsState.setStashed(STASH_TYPE_NONE);
                     } else {
                         // User has stalled long enough for this not to be a drag or a double tap,
@@ -921,9 +926,15 @@
                     && mPipExclusionBoundsChangeListener.get() != null) {
                 mPipExclusionBoundsChangeListener.get().accept(mPipBoundsState.getBounds());
             }
-            if (mPipBoundsState.getBounds().left < 0) {
+            if (mPipBoundsState.getBounds().left < 0
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
                 mPipBoundsState.setStashed(STASH_TYPE_LEFT);
-            } else {
+            } else if (mPipBoundsState.getBounds().left >= 0
+                    && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+                mPipUiEventLogger.log(
+                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
                 mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
new file mode 100644
index 0000000..78af9df
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.RippleDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/** Popup to show the hint about the {@link SizeCompatRestartButton}. */
+public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener {
+
+    private SizeCompatUILayout mLayout;
+
+    public SizeCompatHintPopup(Context context) {
+        super(context);
+    }
+
+    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    void inject(SizeCompatUILayout layout) {
+        mLayout = layout;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        final Button gotItButton = findViewById(R.id.got_it);
+        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
+                null /* content */, null /* mask */));
+        gotItButton.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        mLayout.dismissHint();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
index 9094d7d..08a8402 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -22,19 +22,13 @@
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 
 /** Button to restart the size compat activity. */
@@ -42,10 +36,6 @@
         View.OnLongClickListener {
 
     private SizeCompatUILayout mLayout;
-    private ImageButton mRestartButton;
-    @VisibleForTesting
-    PopupWindow mShowingHint;
-    private WindowManager.LayoutParams mWinParams;
 
     public SizeCompatRestartButton(@NonNull Context context) {
         super(context);
@@ -67,24 +57,19 @@
 
     void inject(SizeCompatUILayout layout) {
         mLayout = layout;
-        mWinParams = layout.getWindowLayoutParams();
-    }
-
-    void remove() {
-        dismissHint();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mRestartButton = findViewById(R.id.size_compat_restart_button);
+        final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
         final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
         final GradientDrawable mask = new GradientDrawable();
         mask.setShape(GradientDrawable.OVAL);
         mask.setColor(color);
-        mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
-        mRestartButton.setOnClickListener(this);
-        mRestartButton.setOnLongClickListener(this);
+        restartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
+        restartButton.setOnClickListener(this);
+        restartButton.setOnLongClickListener(this);
     }
 
     @Override
@@ -94,69 +79,7 @@
 
     @Override
     public boolean onLongClick(View v) {
-        showHint();
+        mLayout.onRestartButtonLongClicked();
         return true;
     }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mLayout.mShouldShowHint) {
-            mLayout.mShouldShowHint = false;
-            showHint();
-        }
-    }
-
-    @Override
-    public void setVisibility(@Visibility int visibility) {
-        if (visibility == View.GONE && mShowingHint != null) {
-            // Also dismiss the popup.
-            dismissHint();
-        }
-        super.setVisibility(visibility);
-    }
-
-    @Override
-    public void setLayoutDirection(int layoutDirection) {
-        final int gravity = SizeCompatUILayout.getGravity(layoutDirection);
-        if (mWinParams.gravity != gravity) {
-            mWinParams.gravity = gravity;
-            getContext().getSystemService(WindowManager.class).updateViewLayout(this,
-                    mWinParams);
-        }
-        super.setLayoutDirection(layoutDirection);
-    }
-
-    void showHint() {
-        if (mShowingHint != null) {
-            return;
-        }
-
-        // TODO: popup is not attached to the button surface. Need to handle this differently for
-        // non-fullscreen task.
-        final View popupView = LayoutInflater.from(getContext()).inflate(
-                R.layout.size_compat_mode_hint, null);
-        final PopupWindow popupWindow = new PopupWindow(popupView,
-                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
-        popupWindow.setWindowLayoutType(mWinParams.type);
-        popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
-        popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
-        popupWindow.setClippingEnabled(false);
-        popupWindow.setOnDismissListener(() -> mShowingHint = null);
-        mShowingHint = popupWindow;
-
-        final Button gotItButton = popupView.findViewById(R.id.got_it);
-        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
-                null /* content */, null /* mask */));
-        gotItButton.setOnClickListener(view -> dismissHint());
-        popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX,
-                mLayout.mPopupOffsetY);
-    }
-
-    void dismissHint() {
-        if (mShowingHint != null) {
-            mShowingHint.dismiss();
-            mShowingHint = null;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index a3880f4..c981ade 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -50,7 +50,7 @@
     /** Whether the IME is shown on display id. */
     private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
 
-    /** The showing buttons by task id. */
+    /** The showing UIs by task id. */
     private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
 
     /** Avoid creating display context frequently for non-default display. */
@@ -77,12 +77,12 @@
     }
 
     /**
-     * Called when the Task info changed. Creates and updates the restart button if there is an
-     * activity in size compat, or removes the restart button if there is no size compat activity.
+     * Called when the Task info changed. Creates and updates the size compat UI if there is an
+     * activity in size compat, or removes the UI if there is no size compat activity.
      *
      * @param displayId display the task and activity are in.
      * @param taskId task the activity is in.
-     * @param taskConfig task config to place the restart button with.
+     * @param taskConfig task config to place the size compat UI with.
      * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
      *                           top activity in this Task is not in size compat.
      * @param taskListener listener to handle the Task Surface placement.
@@ -94,10 +94,10 @@
             // Null token means the current foreground activity is not in size compatibility mode.
             removeLayout(taskId);
         } else if (mActiveLayouts.contains(taskId)) {
-            // Button already exists, update the button layout.
+            // UI already exists, update the UI layout.
             updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener);
         } else {
-            // Create a new restart button.
+            // Create a new size compat UI.
             createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener);
         }
     }
@@ -106,7 +106,7 @@
     public void onDisplayRemoved(int displayId) {
         mDisplayContextCache.remove(displayId);
 
-        // Remove all buttons on the removed display.
+        // Remove all size compat UIs on the removed display.
         final List<Integer> toRemoveTaskIds = new ArrayList<>();
         forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
         for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
@@ -128,7 +128,7 @@
             mDisplaysWithIme.remove(displayId);
         }
 
-        // Hide the button when input method is showing.
+        // Hide the size compat UIs when input method is showing.
         forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index 5924b53..32f3648 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -30,7 +30,6 @@
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
-import android.view.Gravity;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
@@ -43,7 +42,7 @@
 
 /**
  * Records and handles layout of size compat UI on a task with size compat activity. Helps to
- * calculate proper bounds when configuration or button position changes.
+ * calculate proper bounds when configuration or UI position changes.
  */
 class SizeCompatUILayout {
     private static final String TAG = "SizeCompatUILayout";
@@ -56,12 +55,18 @@
     private IBinder mActivityToken;
     private ShellTaskOrganizer.TaskListener mTaskListener;
     private DisplayLayout mDisplayLayout;
-    @VisibleForTesting
-    final SizeCompatUIWindowManager mWindowManager;
 
     @VisibleForTesting
+    final SizeCompatUIWindowManager mButtonWindowManager;
+    @VisibleForTesting
+    @Nullable
+    SizeCompatUIWindowManager mHintWindowManager;
+    @VisibleForTesting
     @Nullable
     SizeCompatRestartButton mButton;
+    @VisibleForTesting
+    @Nullable
+    SizeCompatHintPopup mHint;
     final int mButtonSize;
     final int mPopupOffsetX;
     final int mPopupOffsetY;
@@ -79,7 +84,7 @@
         mTaskListener = taskListener;
         mDisplayLayout = displayLayout;
         mShouldShowHint = !hasShownHint;
-        mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
+        mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
 
         mButtonSize =
                 mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
@@ -87,21 +92,52 @@
         mPopupOffsetY = mButtonSize;
     }
 
-    /** Creates the button window. */
+    /** Creates the activity restart button window. */
     void createSizeCompatButton(boolean isImeShowing) {
         if (isImeShowing || mButton != null) {
             // When ime is showing, wait until ime is dismiss to create UI.
             return;
         }
-        mButton = mWindowManager.createSizeCompatUI();
-        updateSurfacePosition();
+        mButton = mButtonWindowManager.createSizeCompatButton();
+        updateButtonSurfacePosition();
+
+        if (mShouldShowHint) {
+            // Only show by default for the first time.
+            mShouldShowHint = false;
+            createSizeCompatHint();
+        }
     }
 
-    /** Releases the button window. */
+    /** Creates the restart button hint window. */
+    private void createSizeCompatHint() {
+        if (mHint != null) {
+            // Hint already shown.
+            return;
+        }
+        mHintWindowManager = createHintWindowManager();
+        mHint = mHintWindowManager.createSizeCompatHint();
+        updateHintSurfacePosition();
+    }
+
+    @VisibleForTesting
+    SizeCompatUIWindowManager createHintWindowManager() {
+        return new SizeCompatUIWindowManager(mContext, mTaskConfig, this);
+    }
+
+    /** Dismisses the hint window. */
+    void dismissHint() {
+        mHint = null;
+        if (mHintWindowManager != null) {
+            mHintWindowManager.release();
+            mHintWindowManager = null;
+        }
+    }
+
+    /** Releases the UI windows. */
     void release() {
-        mButton.remove();
+        dismissHint();
         mButton = null;
-        mWindowManager.release();
+        mButtonWindowManager.release();
     }
 
     /** Called when size compat info changed. */
@@ -115,7 +151,10 @@
 
         // Update configuration.
         mContext = mContext.createConfigurationContext(taskConfig);
-        mWindowManager.setConfiguration(taskConfig);
+        mButtonWindowManager.setConfiguration(taskConfig);
+        if (mHintWindowManager != null) {
+            mHintWindowManager.setConfiguration(taskConfig);
+        }
 
         if (mButton == null || prevTaskListener != taskListener) {
             // TaskListener changed, recreate the button for new surface parent.
@@ -126,14 +165,19 @@
 
         if (!taskConfig.windowConfiguration.getBounds()
                 .equals(prevTaskConfig.windowConfiguration.getBounds())) {
-            // Reposition the button surface.
-            updateSurfacePosition();
+            // Reposition the UI surfaces.
+            updateButtonSurfacePosition();
+            updateHintSurfacePosition();
         }
 
         if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
             // Update layout for RTL.
             mButton.setLayoutDirection(taskConfig.getLayoutDirection());
-            updateSurfacePosition();
+            updateButtonSurfacePosition();
+            if (mHint != null) {
+                mHint.setLayoutDirection(taskConfig.getLayoutDirection());
+                updateHintSurfacePosition();
+            }
         }
     }
 
@@ -149,8 +193,9 @@
         displayLayout.getStableBounds(curStableBounds);
         mDisplayLayout = displayLayout;
         if (!prevStableBounds.equals(curStableBounds)) {
-            // Stable bounds changed, update button surface position.
-            updateSurfacePosition();
+            // Stable bounds changed, update UI surface positions.
+            updateButtonSurfacePosition();
+            updateHintSurfacePosition();
         }
     }
 
@@ -162,27 +207,46 @@
             return;
         }
 
+        // Hide size compat UIs when IME is showing.
         final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
         if (mButton.getVisibility() != newVisibility) {
             mButton.setVisibility(newVisibility);
         }
+        if (mHint != null && mHint.getVisibility() != newVisibility) {
+            mHint.setVisibility(newVisibility);
+        }
     }
 
     /** Gets the layout params for restart button. */
-    WindowManager.LayoutParams getWindowLayoutParams() {
+    WindowManager.LayoutParams getButtonWindowLayoutParams() {
         final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+                // Cannot be wrap_content as this determines the actual window size
                 mButtonSize, mButtonSize,
                 TYPE_APPLICATION_OVERLAY,
                 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
                 PixelFormat.TRANSLUCENT);
-        winParams.gravity = getGravity(getLayoutDirection());
         winParams.token = new Binder();
-        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId());
+        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId());
         winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
         return winParams;
     }
 
-    /** Called when it is ready to be placed button surface button. */
+    /** Gets the layout params for hint popup. */
+    WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) {
+        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+                // Cannot be wrap_content as this determines the actual window size
+                hint.getMeasuredWidth(), hint.getMeasuredHeight(),
+                TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        winParams.token = new Binder();
+        winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId());
+        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        winParams.windowAnimations = android.R.style.Animation_InputMethod;
+        return winParams;
+    }
+
+    /** Called when it is ready to be placed size compat UI surface. */
     void attachToParentSurface(SurfaceControl.Builder b) {
         mTaskListener.attachChildSurfaceToTask(mTaskId, b);
     }
@@ -192,13 +256,17 @@
         ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken);
     }
 
+    /** Called when the restart button is long clicked. */
+    void onRestartButtonLongClicked() {
+        createSizeCompatHint();
+    }
+
     @VisibleForTesting
-    void updateSurfacePosition() {
-        if (mButton == null || mWindowManager.getSurfaceControl() == null) {
+    void updateButtonSurfacePosition() {
+        if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) {
             return;
         }
-        // The hint popup won't be at the correct position.
-        mButton.dismissHint();
+        final SurfaceControl leash = mButtonWindowManager.getSurfaceControl();
 
         // Use stable bounds to prevent the button from overlapping with system bars.
         final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
@@ -212,8 +280,30 @@
                 : stableBounds.right - taskBounds.left - mButtonSize;
         final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;
 
-        mSyncQueue.runInSync(t ->
-                t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY));
+        mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY));
+    }
+
+    void updateHintSurfacePosition() {
+        if (mHint == null || mHintWindowManager == null
+                || mHintWindowManager.getSurfaceControl() == null) {
+            return;
+        }
+        final SurfaceControl leash = mHintWindowManager.getSurfaceControl();
+
+        // Use stable bounds to prevent the hint from overlapping with system bars.
+        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+        final Rect stableBounds = new Rect();
+        mDisplayLayout.getStableBounds(stableBounds);
+        stableBounds.intersect(taskBounds);
+
+        // Position of the hint in the container coordinate.
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? stableBounds.left - taskBounds.left + mPopupOffsetX
+                : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth();
+        final int positionY =
+                stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight();
+
+        mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY));
     }
 
     int getDisplayId() {
@@ -227,9 +317,4 @@
     private int getLayoutDirection() {
         return mContext.getResources().getConfiguration().getLayoutDirection();
     }
-
-    static int getGravity(int layoutDirection) {
-        return Gravity.BOTTOM
-                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
index a7ad982..f634c45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -24,12 +24,14 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceSession;
+import android.view.View;
 import android.view.WindowlessWindowManager;
 
 import com.android.wm.shell.R;
 
 /**
- * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}.
+ * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton} or
+ * {@link SizeCompatHintPopup}.
  */
 class SizeCompatUIWindowManager extends WindowlessWindowManager {
 
@@ -67,18 +69,39 @@
     }
 
     /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
-    SizeCompatRestartButton createSizeCompatUI() {
-        if (mViewHost == null) {
-            mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+    SizeCompatRestartButton createSizeCompatButton() {
+        if (mViewHost != null) {
+            throw new IllegalStateException(
+                    "A UI has already been created with this window manager.");
         }
 
+        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
         final SizeCompatRestartButton button = (SizeCompatRestartButton)
                 LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
         button.inject(mLayout);
-        mViewHost.setView(button, mLayout.getWindowLayoutParams());
+        mViewHost.setView(button, mLayout.getButtonWindowLayoutParams());
         return button;
     }
 
+    /** Inflates {@link SizeCompatHintPopup} on to the root surface. */
+    SizeCompatHintPopup createSizeCompatHint() {
+        if (mViewHost != null) {
+            throw new IllegalStateException(
+                    "A UI has already been created with this window manager.");
+        }
+
+        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+        final SizeCompatHintPopup hint = (SizeCompatHintPopup)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
+        // Measure how big the hint is.
+        hint.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+        hint.inject(mLayout);
+        mViewHost.setView(hint, mLayout.getHintWindowLayoutParams(hint));
+        return hint;
+    }
+
     /** Releases the surface control and tears down the view hierarchy. */
     void release() {
         if (mViewHost != null) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
new file mode 100644
index 0000000..9845d46
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.widget.Button;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatHintPopup}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatHintPopupTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatHintPopupTest extends ShellTestCase {
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private IBinder mActivityToken;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private DisplayLayout mDisplayLayout;
+
+    private SizeCompatUILayout mLayout;
+    private SizeCompatHintPopup mHint;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final int taskId = 1;
+        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+                taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+        mHint = (SizeCompatHintPopup)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
+        mHint.inject(mLayout);
+
+        spyOn(mLayout);
+    }
+
+    @Test
+    public void testOnClick() {
+        doNothing().when(mLayout).dismissHint();
+
+        final Button button = mHint.findViewById(R.id.got_it);
+        button.performClick();
+
+        verify(mLayout).dismissHint();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
index d9086a6..5a43925 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
@@ -19,13 +19,13 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.res.Configuration;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
 import android.view.LayoutInflater;
+import android.widget.ImageButton;
 
 import androidx.test.filters.SmallTest;
 
@@ -71,45 +71,25 @@
         mButton.inject(mLayout);
 
         spyOn(mLayout);
-        spyOn(mButton);
-        doNothing().when(mButton).showHint();
     }
 
     @Test
     public void testOnClick() {
         doNothing().when(mLayout).onRestartButtonClicked();
 
-        mButton.onClick(mButton);
+        final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
+        button.performClick();
 
         verify(mLayout).onRestartButtonClicked();
     }
 
     @Test
     public void testOnLongClick() {
-        verify(mButton, never()).showHint();
+        doNothing().when(mLayout).onRestartButtonLongClicked();
 
-        mButton.onLongClick(mButton);
+        final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
+        button.performLongClick();
 
-        verify(mButton).showHint();
-    }
-
-    @Test
-    public void testOnAttachedToWindow_showHint() {
-        mLayout.mShouldShowHint = false;
-        mButton.onAttachedToWindow();
-
-        verify(mButton, never()).showHint();
-
-        mLayout.mShouldShowHint = true;
-        mButton.onAttachedToWindow();
-
-        verify(mButton).showHint();
-    }
-
-    @Test
-    public void testRemove() {
-        mButton.remove();
-
-        verify(mButton).dismissHint();
+        verify(mLayout).onRestartButtonLongClicked();
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
index 236db44..f33cfe8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -27,6 +28,7 @@
 import static org.mockito.Mockito.doReturn;
 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 android.app.ActivityClient;
@@ -68,6 +70,7 @@
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock private DisplayLayout mDisplayLayout;
     @Mock private SizeCompatRestartButton mButton;
+    @Mock private SizeCompatHintPopup mHint;
     private Configuration mTaskConfig;
 
     private SizeCompatUILayout mLayout;
@@ -81,8 +84,13 @@
                 TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
 
         spyOn(mLayout);
-        spyOn(mLayout.mWindowManager);
-        doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI();
+        spyOn(mLayout.mButtonWindowManager);
+        doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton();
+
+        final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager();
+        spyOn(hintWindowManager);
+        doReturn(mHint).when(hintWindowManager).createSizeCompatHint();
+        doReturn(hintWindowManager).when(mLayout).createHintWindowManager();
     }
 
     @Test
@@ -90,24 +98,45 @@
         // Not create button if IME is showing.
         mLayout.createSizeCompatButton(true /* isImeShowing */);
 
-        verify(mLayout.mWindowManager, never()).createSizeCompatUI();
+        verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton();
         assertNull(mLayout.mButton);
+        assertNull(mLayout.mHintWindowManager);
+        assertNull(mLayout.mHint);
 
+        // Not create hint popup.
+        mLayout.mShouldShowHint = false;
         mLayout.createSizeCompatButton(false /* isImeShowing */);
 
-        verify(mLayout.mWindowManager).createSizeCompatUI();
+        verify(mLayout.mButtonWindowManager).createSizeCompatButton();
         assertNotNull(mLayout.mButton);
+        assertNull(mLayout.mHintWindowManager);
+        assertNull(mLayout.mHint);
+
+        // Create hint popup.
+        mLayout.release();
+        mLayout.mShouldShowHint = true;
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton();
+        assertNotNull(mLayout.mButton);
+        assertNotNull(mLayout.mHintWindowManager);
+        verify(mLayout.mHintWindowManager).createSizeCompatHint();
+        assertNotNull(mLayout.mHint);
+        assertFalse(mLayout.mShouldShowHint);
     }
 
     @Test
     public void testRelease() {
         mLayout.createSizeCompatButton(false /* isImeShowing */);
+        final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
 
         mLayout.release();
 
         assertNull(mLayout.mButton);
-        verify(mButton).remove();
-        verify(mLayout.mWindowManager).release();
+        assertNull(mLayout.mHint);
+        verify(hintWindowManager).release();
+        assertNull(mLayout.mHintWindowManager);
+        verify(mLayout.mButtonWindowManager).release();
     }
 
     @Test
@@ -119,7 +148,7 @@
         mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener,
                 false /* isImeShowing */);
 
-        verify(mLayout, never()).updateSurfacePosition();
+        verify(mLayout, never()).updateButtonSurfacePosition();
         verify(mLayout, never()).release();
         verify(mLayout, never()).createSizeCompatButton(anyBoolean());
 
@@ -140,7 +169,8 @@
         mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener,
                 false /* isImeShowing */);
 
-        verify(mLayout).updateSurfacePosition();
+        verify(mLayout).updateButtonSurfacePosition();
+        verify(mLayout).updateHintSurfacePosition();
     }
 
     @Test
@@ -152,14 +182,16 @@
                 mContext.getResources(), false, false);
 
         mLayout.updateDisplayLayout(displayLayout1);
-        verify(mLayout).updateSurfacePosition();
+        verify(mLayout).updateButtonSurfacePosition();
+        verify(mLayout).updateHintSurfacePosition();
 
         // No update if the display bounds is the same.
         clearInvocations(mLayout);
         final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
                 mContext.getResources(), false, false);
         mLayout.updateDisplayLayout(displayLayout2);
-        verify(mLayout, never()).updateSurfacePosition();
+        verify(mLayout, never()).updateButtonSurfacePosition();
+        verify(mLayout, never()).updateHintSurfacePosition();
     }
 
     @Test
@@ -203,4 +235,29 @@
 
         verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken);
     }
+
+    @Test
+    public void testOnRestartButtonLongClicked_showHint() {
+        mLayout.dismissHint();
+
+        assertNull(mLayout.mHint);
+
+        mLayout.onRestartButtonLongClicked();
+
+        assertNotNull(mLayout.mHint);
+    }
+
+    @Test
+    public void testDismissHint() {
+        mLayout.onRestartButtonLongClicked();
+        final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
+        assertNotNull(mLayout.mHint);
+        assertNotNull(hintWindowManager);
+
+        mLayout.dismissHint();
+
+        assertNull(mLayout.mHint);
+        assertNull(mLayout.mHintWindowManager);
+        verify(hintWindowManager).release();
+    }
 }
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index b39f4f2..0bf9480 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -249,5 +249,20 @@
     mHead->pendingDirty.setEmpty();
 }
 
+const StretchEffect* DamageAccumulator::findNearestStretchEffect() const {
+    DirtyStack* frame = mHead;
+    while (frame->prev != frame) {
+        frame = frame->prev;
+        if (frame->type == TransformRenderNode) {
+            const auto& effect =
+                    frame->renderNode->properties().layerProperties().getStretchEffect();
+            if (!effect.isEmpty()) {
+                return &effect;
+            }
+        }
+    }
+    return nullptr;
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 2faa9d0..89ee0e3 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -35,6 +35,7 @@
 struct DirtyStack;
 class RenderNode;
 class Matrix4;
+class StretchEffect;
 
 class DamageAccumulator {
     PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
@@ -62,6 +63,8 @@
 
     void finish(SkRect* totalDirty);
 
+    const StretchEffect* findNearestStretchEffect() const;
+
 private:
     void pushCommon();
     void applyMatrix4Transform(DirtyStack* frame);
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 4a2e30d..2cd9b7b 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -236,7 +236,6 @@
 }
 
 void JankTracker::reset() {
-    std::lock_guard lock(mDataMutex);
     mFrames.clear();
     mData->reset();
     (*mGlobalData)->reset();
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 64b8b71..170f731 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -384,10 +384,11 @@
 struct DrawTextBlob final : Op {
     static const auto kType = Type::DrawTextBlob;
     DrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
-            : blob(sk_ref_sp(blob)), x(x), y(y), paint(paint) {}
+        : blob(sk_ref_sp(blob)), x(x), y(y), paint(paint), drawTextBlobMode(gDrawTextBlobMode) {}
     sk_sp<const SkTextBlob> blob;
     SkScalar x, y;
     SkPaint paint;
+    DrawTextBlobMode drawTextBlobMode;
     void draw(SkCanvas* c, const SkMatrix&) const { c->drawTextBlob(blob.get(), x, y, paint); }
 };
 
@@ -832,6 +833,24 @@
     }
 }
 
+template<>
+constexpr color_transform_fn colorTransformForOp<DrawTextBlob>() {
+    return [](const void *opRaw, ColorTransform transform) {
+        const DrawTextBlob *op = reinterpret_cast<const DrawTextBlob*>(opRaw);
+        switch (op->drawTextBlobMode) {
+        case DrawTextBlobMode::HctOutline:
+            const_cast<SkPaint&>(op->paint).setColor(SK_ColorBLACK);
+            break;
+        case DrawTextBlobMode::HctInner:
+            const_cast<SkPaint&>(op->paint).setColor(SK_ColorWHITE);
+            break;
+        default:
+            transformPaint(transform, const_cast<SkPaint*>(&(op->paint)));
+            break;
+        }
+    };
+}
+
 #define X(T) colorTransformForOp<T>(),
 static const color_transform_fn color_transform_fns[] = {
 #include "DisplayListOps.in"
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 44f54ee..f5b2675 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -226,6 +226,9 @@
     if (!mProperties.getAllowForceDark()) {
         info.disableForceDark++;
     }
+    if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
+        info.stretchEffectCount++;
+    }
 
     uint32_t animatorDirtyMask = 0;
     if (CC_LIKELY(info.runAnimations)) {
@@ -267,6 +270,9 @@
     if (!mProperties.getAllowForceDark()) {
         info.disableForceDark--;
     }
+    if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
+        info.stretchEffectCount--;
+    }
     info.damageAccumulator->popTransform();
 }
 
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 8fba9cf..0589f13 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -70,6 +70,7 @@
     setXferMode(other.xferMode());
     setColorFilter(other.getColorFilter());
     setImageFilter(other.getImageFilter());
+    mStretchEffect = other.mStretchEffect;
     return *this;
 }
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index f2481f8..cc9094c 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -98,6 +98,8 @@
 
     const SkISize screenSize;
 
+    int stretchEffectCount = 0;
+
     struct Out {
         bool hasFunctors = false;
         // This is only updated if evaluateAnimations is true
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 146bf28..b046f45 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -110,16 +110,19 @@
             bool darken = channelSum < (128 * 3);
 
             // outline
+            gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
             Paint outlinePaint(paint);
             simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
             outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
 
             // inner
+            gDrawTextBlobMode = DrawTextBlobMode::HctInner;
             Paint innerPaint(paint);
             simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
             innerPaint.setStyle(SkPaint::kFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+            gDrawTextBlobMode = DrawTextBlobMode::Normal;
         } else {
             // standard draw path
             canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index fdfa288..d1bdb71 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -61,6 +61,14 @@
 class Paint;
 struct Typeface;
 
+enum class DrawTextBlobMode {
+    Normal,
+    HctOutline,
+    HctInner,
+};
+
+inline DrawTextBlobMode gDrawTextBlobMode = DrawTextBlobMode::Normal;
+
 class ANDROID_API Canvas {
 public:
     virtual ~Canvas(){};
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8023968..5f60437 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -25,6 +25,7 @@
 #include <renderthread/CanvasContext.h>
 #endif
 #include <TreeInfo.h>
+#include <effects/StretchEffect.h>
 #include <hwui/Paint.h>
 #include <utils/TraceUtils.h>
 
@@ -549,6 +550,7 @@
 // ----------------------------------------------------------------------------
 
 jmethodID gPositionListener_PositionChangedMethod;
+jmethodID gPositionListener_ApplyStretchMethod;
 jmethodID gPositionListener_PositionLostMethod;
 
 static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
@@ -571,6 +573,11 @@
             Matrix4 transform;
             info.damageAccumulator->computeCurrentTransform(&transform);
             const RenderProperties& props = node.properties();
+
+            if (info.stretchEffectCount) {
+                handleStretchEffect(info, transform);
+            }
+
             uirenderer::Rect bounds(props.getWidth(), props.getHeight());
             transform.mapRect(bounds);
 
@@ -613,7 +620,7 @@
             JNIEnv* env = jnienv();
             jobject localref = env->NewLocalRef(mWeakRef);
             if (CC_UNLIKELY(!localref)) {
-                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                env->DeleteWeakGlobalRef(mWeakRef);
                 mWeakRef = nullptr;
                 return;
             }
@@ -634,6 +641,32 @@
             return env;
         }
 
+        void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
+            // Search up to find the nearest stretcheffect parent
+            const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect();
+            if (!effect) {
+                return;
+            }
+
+            uirenderer::Rect area = effect->stretchArea;
+            transform.mapRect(area);
+            JNIEnv* env = jnienv();
+
+            jobject localref = env->NewLocalRef(mWeakRef);
+            if (CC_UNLIKELY(!localref)) {
+                env->DeleteWeakGlobalRef(mWeakRef);
+                mWeakRef = nullptr;
+                return;
+            }
+#ifdef __ANDROID__  // Layoutlib does not support CanvasContext
+            env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
+                                info.canvasContext.getFrameNumber(), area.left, area.top,
+                                area.right, area.bottom, effect->stretchDirection.fX,
+                                effect->stretchDirection.fY, effect->maxStretchAmount);
+#endif
+            env->DeleteLocalRef(localref);
+        }
+
         void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
                 jint right, jint bottom) {
             ATRACE_NAME("Update SurfaceView position");
@@ -775,6 +808,8 @@
     jclass clazz = FindClassOrDie(env, "android/graphics/RenderNode$PositionUpdateListener");
     gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
             "positionChanged", "(JIIII)V");
+    gPositionListener_ApplyStretchMethod =
+            GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V");
     gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
             "positionLost", "(J)V");
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index b288402..04e3a1c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -194,25 +194,6 @@
     return filterPaint(std::move(paint));
 }
 
-static BlurDrawLooper* get_looper(const Paint* paint) {
-    return paint ? paint->getLooper() : nullptr;
-}
-
-template <typename Proc>
-void applyLooper(BlurDrawLooper* looper, const SkPaint* paint, Proc proc) {
-    if (looper) {
-        SkPaint p;
-        if (paint) {
-            p = *paint;
-        }
-        looper->apply(p, [&](SkPoint offset, const SkPaint& modifiedPaint) {
-            proc(offset.fX, offset.fY, &modifiedPaint);
-        });
-    } else {
-        proc(0, 0, paint);
-    }
-}
-
 static SkFilterMode Paint_to_filter(const SkPaint* paint) {
     return paint && paint->getFilterQuality() != kNone_SkFilterQuality ? SkFilterMode::kLinear
                                                                        : SkFilterMode::kNearest;
@@ -226,8 +207,7 @@
 void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
     sk_sp<SkImage> image = bitmap.makeImage();
 
-    applyLooper(get_looper(paint), filterBitmap(paint), [&](SkScalar x, SkScalar y,
-                const SkPaint* p) {
+    applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) {
         mRecorder.drawImage(image, left + x, top + y, Paint_to_sampling(p), p, bitmap.palette());
     });
 
@@ -245,8 +225,7 @@
 
     sk_sp<SkImage> image = bitmap.makeImage();
 
-    applyLooper(get_looper(paint), filterBitmap(paint), [&](SkScalar x, SkScalar y,
-                const SkPaint* p) {
+    applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) {
         mRecorder.drawImage(image, x, y, Paint_to_sampling(p), p, bitmap.palette());
     });
 
@@ -263,8 +242,7 @@
 
     sk_sp<SkImage> image = bitmap.makeImage();
 
-    applyLooper(get_looper(paint), filterBitmap(paint), [&](SkScalar x, SkScalar y,
-                const SkPaint* p) {
+    applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) {
         mRecorder.drawImageRect(image, srcRect, dstRect.makeOffset(x, y), Paint_to_sampling(p),
                                 p, SkCanvas::kFast_SrcRectConstraint, bitmap.palette());
     });
@@ -303,8 +281,7 @@
     // HWUI always draws 9-patches with linear filtering, regardless of the Paint.
     const SkFilterMode filter = SkFilterMode::kLinear;
 
-    applyLooper(get_looper(paint), filterBitmap(paint), [&](SkScalar x, SkScalar y,
-                const SkPaint* p) {
+    applyLooper(paint, [&](SkScalar x, SkScalar y, const SkPaint* p) {
         mRecorder.drawImageLattice(image, lattice, dst.makeOffset(x, y), filter, p,
                                    bitmap.palette());
     });
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 8d7a21a..1e404b8 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -87,6 +87,23 @@
     std::unique_ptr<SkiaDisplayList> mDisplayList;
     StartReorderBarrierDrawable* mCurrentBarrier;
 
+    template <typename Proc>
+    void applyLooper(const Paint* paint, Proc proc) {
+        SkPaint skp;
+        BlurDrawLooper* looper = nullptr;
+        if (paint) {
+            skp = *filterBitmap(paint);
+            looper = paint->getLooper();
+        }
+        if (looper) {
+            looper->apply(skp, [&](SkPoint offset, const SkPaint& modifiedPaint) {
+                proc(offset.fX, offset.fY, &modifiedPaint);
+            });
+        } else {
+            proc(0, 0, &skp);
+        }
+    }
+
     /**
      *  A new SkiaDisplayList is created or recycled if available.
      *
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b9568fc..423cc08 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -230,7 +230,10 @@
 }
 
 void RenderProxy::resetProfileInfo() {
-    mRenderThread.queue().runSync([=]() { mContext->resetFrameStats(); });
+    mRenderThread.queue().runSync([=]() {
+        std::lock_guard lock(mRenderThread.getJankDataMutex());
+        mContext->resetFrameStats();
+    });
 }
 
 uint32_t RenderProxy::frameTimePercentile(int percentile) {
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 6a9a98d..898c64b 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -22,16 +22,16 @@
 namespace uirenderer {
 namespace test {
 
-const DisplayInfo& getDisplayInfo() {
-    static DisplayInfo info = [] {
-        DisplayInfo info;
+const ui::StaticDisplayInfo& getDisplayInfo() {
+    static ui::StaticDisplayInfo info = [] {
+        ui::StaticDisplayInfo info;
 #if HWUI_NULL_GPU
         info.density = 2.f;
 #else
         const sp<IBinder> token = SurfaceComposerClient::getInternalDisplayToken();
         LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
 
-        const status_t status = SurfaceComposerClient::getDisplayInfo(token, &info);
+        const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info);
         LOG_ALWAYS_FATAL_IF(status, "%s: Failed to get display info", __FUNCTION__);
 #endif
         return info;
diff --git a/libs/hwui/tests/common/TestContext.h b/libs/hwui/tests/common/TestContext.h
index 7d2f6d8..9d00366d 100644
--- a/libs/hwui/tests/common/TestContext.h
+++ b/libs/hwui/tests/common/TestContext.h
@@ -23,8 +23,8 @@
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
-#include <ui/DisplayInfo.h>
 #include <ui/DisplayMode.h>
+#include <ui/StaticDisplayInfo.h>
 #include <utils/Looper.h>
 
 #include <atomic>
@@ -36,7 +36,7 @@
 namespace uirenderer {
 namespace test {
 
-const DisplayInfo& getDisplayInfo();
+const ui::StaticDisplayInfo& getDisplayInfo();
 const ui::DisplayMode& getActiveDisplayMode();
 
 inline const ui::Size& getActiveDisplayResolution() {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index e6dfc4c..c0ab58b 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -20,7 +20,6 @@
 #include <gui/DisplayEventReceiver.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
-#include <ui/DisplayInfo.h>
 #include <utils/BitSet.h>
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 827fcf1..97567ba 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -21,7 +21,6 @@
 #include <gui/DisplayEventReceiver.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
-#include <ui/DisplayInfo.h>
 #include <utils/BitSet.h>
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 98073fe..26a65a4 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -21,7 +21,6 @@
 #include <gui/DisplayEventReceiver.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
-#include <ui/DisplayInfo.h>
 #include <utils/BitSet.h>
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 3dcaffb..242d9a3 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1096,13 +1096,7 @@
      * Gets the carrier frequency of the tracked signal.
      *
      * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz,
-     * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
-     * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
-     *
-     * <p> For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two raw
-     * measurement objects will be reported for this same satellite, in one of the measurement
-     * objects, all the values related to L1 will be filled, and in the other all of the values
-     * related to L5 will be filled.
+     * L5 = 1176.45 MHz, varying GLO channels, etc.
      *
      * <p>The value is only available if {@link #hasCarrierFrequencyHz()} is {@code true}.
      *
@@ -1382,7 +1376,8 @@
      * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
      * level may be used to indicate potential interference. Higher gain (and/or lower input power)
      * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
-     * signal, this value will go more negative.
+     * signal, this value will go more negative. This value must be consistent given the same level
+     * of the incoming signal power.
      *
      * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
      * components) may also affect the typical output of of this value on any given hardware design
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index b46e8ce..23390fc 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -284,12 +284,7 @@
      * Gets the carrier frequency of the signal tracked.
      *
      * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60
-     * MHz, L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary
-     * common use central frequency, e.g. L1 = 1575.45 MHz for GPS.
-     *
-     * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two measurements
-     * will be reported for this same satellite, in one all the values related to L1 will be
-     * filled, and in the other all of the values related to L5 will be filled.
+     * MHz, L5 = 1176.45 MHz, varying GLO channels, etc.
      *
      * <p>The value is only available if {@link #hasCarrierFrequencyHz(int satelliteIndex)} is
      * {@code true}.
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 088b789..b782340 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -20,7 +20,6 @@
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.LOCATION_HARDWARE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.location.GpsStatus.GPS_EVENT_STARTED;
 import static android.location.LocationRequest.createFromDeprecatedCriteria;
 import static android.location.LocationRequest.createFromDeprecatedProvider;
 
@@ -412,7 +411,7 @@
     private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
             "cache_key.location_enabled";
 
-    private static ILocationManager getService() throws RemoteException {
+    static ILocationManager getService() throws RemoteException {
         try {
             return ILocationManager.Stub.asInterface(
                     ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE));
@@ -439,11 +438,13 @@
                 new GnssNavigationTransportManager();
     }
 
-    private static final ProviderRequestTransportManager sProviderRequestListeners =
-            new ProviderRequestTransportManager();
+    private static class ProviderRequestLazyLoader {
+        static final ProviderRequestTransportManager sProviderRequestListeners =
+                new ProviderRequestTransportManager();
+    }
 
-    private final Context mContext;
-    private final ILocationManager mService;
+    final Context mContext;
+    final ILocationManager mService;
 
     private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
             new PropertyInvalidatedCache<Integer, Boolean>(
@@ -2790,7 +2791,7 @@
     public boolean registerProviderRequestListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Listener listener) {
-        sProviderRequestListeners.addListener(listener,
+        ProviderRequestLazyLoader.sProviderRequestListeners.addListener(listener,
                 new ProviderRequestTransport(executor, listener));
         return true;
     }
@@ -2805,7 +2806,7 @@
     @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
     public void unregisterProviderRequestListener(
             @NonNull Listener listener) {
-        sProviderRequestListeners.removeListener(listener);
+        ProviderRequestLazyLoader.sProviderRequestListeners.removeListener(listener);
     }
 
     /**
@@ -2917,6 +2918,10 @@
     private static class GnssStatusTransportManager extends
             ListenerTransportManager<GnssStatusTransport> {
 
+        GnssStatusTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(GnssStatusTransport transport)
                             throws RemoteException {
@@ -2934,6 +2939,10 @@
     private static class GnssNmeaTransportManager extends
             ListenerTransportManager<GnssNmeaTransport> {
 
+        GnssNmeaTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(GnssNmeaTransport transport)
                             throws RemoteException {
@@ -2951,6 +2960,10 @@
     private static class GnssMeasurementsTransportManager extends
             ListenerTransportManager<GnssMeasurementsTransport> {
 
+        GnssMeasurementsTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(GnssMeasurementsTransport transport)
                             throws RemoteException {
@@ -2968,6 +2981,10 @@
     private static class GnssAntennaTransportManager extends
             ListenerTransportManager<GnssAntennaInfoTransport> {
 
+        GnssAntennaTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(GnssAntennaInfoTransport transport) {
             transport.getContext().registerReceiver(transport,
@@ -2983,6 +3000,10 @@
     private static class GnssNavigationTransportManager extends
             ListenerTransportManager<GnssNavigationTransport> {
 
+        GnssNavigationTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(GnssNavigationTransport transport)
                             throws RemoteException {
@@ -3000,6 +3021,10 @@
     private static class ProviderRequestTransportManager extends
             ListenerTransportManager<ProviderRequestTransport> {
 
+        ProviderRequestTransportManager() {
+            super(false);
+        }
+
         @Override
         protected void registerTransport(ProviderRequestTransport transport)
                 throws RemoteException {
@@ -3117,6 +3142,8 @@
         }
     }
 
+    /** @deprecated */
+    @Deprecated
     private static class GpsAdapter extends GnssStatus.Callback {
 
         private final GpsStatus.Listener mGpsListener;
@@ -3127,7 +3154,7 @@
 
         @Override
         public void onStarted() {
-            mGpsListener.onGpsStatusChanged(GPS_EVENT_STARTED);
+            mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
         }
 
         @Override
@@ -3204,6 +3231,8 @@
         }
     }
 
+    /** @deprecated */
+    @Deprecated
     private static class GpsStatusTransport extends GnssStatusTransport {
 
         static volatile int sTtff;
@@ -3442,6 +3471,8 @@
         }
     }
 
+    /** @deprecated */
+    @Deprecated
     private static class BatchedLocationCallbackWrapper implements LocationListener {
 
         private final BatchedLocationCallback mCallback;
@@ -3461,6 +3492,8 @@
         }
     }
 
+    /** @deprecated */
+    @Deprecated
     private static class BatchedLocationCallbackTransport extends LocationListenerTransport {
 
         BatchedLocationCallbackTransport(BatchedLocationCallback callback, Handler handler) {
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 205c1f4..383c93d 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,8 +16,10 @@
 
 package android.media;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.util.SparseIntArray;
 
 import java.lang.annotation.Retention;
@@ -161,6 +163,14 @@
      */
     public static final int TYPE_BLE_SPEAKER   = 27;
 
+    /**
+     * A device type describing an Echo Canceller loopback Reference.
+     * This device is only used when capturing with MediaRecorder.AudioSource.ECHO_REFERENCE,
+     * which requires privileged permission
+     * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT}.
+     * @hide */
+    @RequiresPermission(Manifest.permission.CAPTURE_AUDIO_OUTPUT)
+    public static final int TYPE_ECHO_REFERENCE   = 28;
 
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
@@ -188,7 +198,8 @@
             TYPE_FM_TUNER,
             TYPE_TV_TUNER,
             TYPE_BLE_HEADSET,
-            TYPE_BLE_SPEAKER}
+            TYPE_BLE_SPEAKER,
+            TYPE_ECHO_REFERENCE}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -211,7 +222,8 @@
             TYPE_LINE_DIGITAL,
             TYPE_IP,
             TYPE_BUS,
-            TYPE_BLE_HEADSET}
+            TYPE_BLE_HEADSET,
+            TYPE_ECHO_REFERENCE}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeIn {}
@@ -297,6 +309,7 @@
             case TYPE_BUS:
             case TYPE_REMOTE_SUBMIX:
             case TYPE_BLE_HEADSET:
+            case TYPE_ECHO_REFERENCE:
                 return true;
             default:
                 return false;
@@ -621,6 +634,8 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLE_HEADSET, TYPE_BLE_HEADSET);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_ECHO_REFERENCE, TYPE_ECHO_REFERENCE);
+
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -678,6 +693,9 @@
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
                 TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_IN_REMOTE_SUBMIX);
         EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_IN_BLE_HEADSET);
+        EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+                TYPE_ECHO_REFERENCE, AudioSystem.DEVICE_IN_ECHO_REFERENCE);
+
     }
 }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d896c1f..f87f90d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3123,52 +3123,57 @@
 
     /**
      * @hide Home sound
-     * Played by the framework when the home app becomes active if config_enableHomeSound is set to
-     * true. This is currently only used on TV devices.
+     * <p>
+     * To be played by the framework when the home app becomes active if config_enableHomeSound is
+     * set to true. This is currently only used on TV devices.
      * Note that this sound is only available if a sound file is specified in audio_assets.xml.
      * @see #playSoundEffect(int)
      */
     public static final int FX_HOME = 11;
 
     /**
-     * @hide Fast scroll sound 1
-     * To be by the framework when a fast-scrolling is performed and
-     * {@link #areFastScrollSoundEffectsEnabled()} is true.
+     * @hide Navigation repeat sound 1
+     * <p>
+     * To be played by the framework when a focus navigation is repeatedly triggered
+     * (e.g. due to long-pressing) and {@link #areNavigationRepeatSoundEffectsEnabled()} is true.
      * This is currently only used on TV devices.
      * Note that this sound is only available if a sound file is specified in audio_assets.xml
      * @see #playSoundEffect(int)
      */
-    public static final int FX_FAST_SCROLL_1 = 12;
+    public static final int FX_FOCUS_NAVIGATION_REPEAT_1 = 12;
 
     /**
-     * @hide Fast scroll sound 2
-     * To be by the framework when a fast-scrolling is performed and
-     * {@link #areFastScrollSoundEffectsEnabled()} is true.
+     * @hide Navigation repeat sound 2
+     * <p>
+     * To be played by the framework when a focus navigation is repeatedly triggered
+     * (e.g. due to long-pressing) and {@link #areNavigationRepeatSoundEffectsEnabled()} is true.
      * This is currently only used on TV devices.
      * Note that this sound is only available if a sound file is specified in audio_assets.xml
      * @see #playSoundEffect(int)
      */
-    public static final int FX_FAST_SCROLL_2 = 13;
+    public static final int FX_FOCUS_NAVIGATION_REPEAT_2 = 13;
 
     /**
-     * @hide Fast scroll sound 3
-     * To be by the framework when a fast-scrolling is performed and
-     * {@link #areFastScrollSoundEffectsEnabled()} is true.
+     * @hide Navigation repeat sound 3
+     * <p>
+     * To be played by the framework when a focus navigation is repeatedly triggered
+     * (e.g. due to long-pressing) and {@link #areNavigationRepeatSoundEffectsEnabled()} is true.
      * This is currently only used on TV devices.
      * Note that this sound is only available if a sound file is specified in audio_assets.xml
      * @see #playSoundEffect(int)
      */
-    public static final int FX_FAST_SCROLL_3 = 14;
+    public static final int FX_FOCUS_NAVIGATION_REPEAT_3 = 14;
 
     /**
-     * @hide Fast scroll sound 4
-     * To be by the framework when a fast-scrolling is performed and
-     * {@link #areFastScrollSoundEffectsEnabled()} is true.
+     * @hide Navigation repeat sound 4
+     * <p>
+     * To be played by the framework when a focus navigation is repeatedly triggered
+     * (e.g. due to long-pressing) and {@link #areNavigationRepeatSoundEffectsEnabled()} is true.
      * This is currently only used on TV devices.
      * Note that this sound is only available if a sound file is specified in audio_assets.xml
      * @see #playSoundEffect(int)
      */
-    public static final int FX_FAST_SCROLL_4 = 15;
+    public static final int FX_FOCUS_NAVIGATION_REPEAT_4 = 15;
 
     /**
      * @hide Number of sound effects
@@ -3177,27 +3182,27 @@
     public static final int NUM_SOUND_EFFECTS = 16;
 
     /**
-     * @hide Number of fast scroll sound effects
+     * @hide Number of FX_FOCUS_NAVIGATION_REPEAT_* sound effects
      */
-    public static final int NUM_FAST_SCROLL_SOUND_EFFECTS = 4;
+    public static final int NUM_NAVIGATION_REPEAT_SOUND_EFFECTS = 4;
 
     /**
      * @hide
-     * @param n a value in [0, {@link #NUM_FAST_SCROLL_SOUND_EFFECTS}[
-     * @return The id of a fast scroll sound effect or -1 if out of bounds
+     * @param n a value in [0, {@link #NUM_NAVIGATION_REPEAT_SOUND_EFFECTS}[
+     * @return The id of a navigation repeat sound effect or -1 if out of bounds
      */
-    public static int getNthFastScrollSoundEffectId(int n) {
+    public static int getNthNavigationRepeatSoundEffect(int n) {
         switch (n) {
             case 0:
-                return FX_FAST_SCROLL_1;
+                return FX_FOCUS_NAVIGATION_REPEAT_1;
             case 1:
-                return FX_FAST_SCROLL_2;
+                return FX_FOCUS_NAVIGATION_REPEAT_2;
             case 2:
-                return FX_FAST_SCROLL_3;
+                return FX_FOCUS_NAVIGATION_REPEAT_3;
             case 3:
-                return FX_FAST_SCROLL_4;
+                return FX_FOCUS_NAVIGATION_REPEAT_4;
             default:
-                Log.w(TAG, "Invalid fast-scroll sound effect id: " + n);
+                Log.w(TAG, "Invalid navigation repeat sound effect id: " + n);
                 return -1;
         }
     }
@@ -3205,9 +3210,9 @@
     /**
      * @hide
      */
-    public void setFastScrollSoundEffectsEnabled(boolean enabled) {
+    public void setNavigationRepeatSoundEffectsEnabled(boolean enabled) {
         try {
-            getService().setFastScrollSoundEffectsEnabled(enabled);
+            getService().setNavigationRepeatSoundEffectsEnabled(enabled);
         } catch (RemoteException e) {
 
         }
@@ -3215,11 +3220,11 @@
 
     /**
      * @hide
-     * @return true if the fast scroll sound effects are enabled
+     * @return true if the navigation repeat sound effects are enabled
      */
-    public boolean areFastScrollSoundEffectsEnabled() {
+    public boolean areNavigationRepeatSoundEffectsEnabled() {
         try {
-            return getService().areFastScrollSoundEffectsEnabled();
+            return getService().areNavigationRepeatSoundEffectsEnabled();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 7fb83f1..6fcb756 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -565,6 +565,11 @@
      */
     private int mOffloadPaddingFrames = 0;
 
+    /**
+     * The log session id used for metrics.
+     */
+    private String mLogSessionId;
+
     //--------------------------------
     // Used exclusively by native code
     //--------------------
@@ -837,6 +842,7 @@
         }
 
         baseRegisterPlayer(mSessionId);
+        native_setPlayerIId(mPlayerIId); // mPlayerIId now ready to send to native AudioTrack.
     }
 
     /**
@@ -3967,6 +3973,23 @@
         }
     }
 
+    /**
+     * Sets a string handle to this AudioTrack for metrics collection.
+     *
+     * @param logSessionId a string which is used to identify this object
+     *        to the metrics service.
+     * @throws IllegalStateException if AudioTrack not initialized.
+     *
+     * @hide
+     */
+    public void setLogSessionId(@NonNull String logSessionId) {
+        if (mState == STATE_UNINITIALIZED) {
+            throw new IllegalStateException("track not initialized");
+        }
+        native_setLogSessionId(logSessionId);
+        mLogSessionId = logSessionId;
+    }
+
     //---------------------------------------------------------
     // Inner classes
     //--------------------
@@ -4202,6 +4225,21 @@
     private native int native_get_audio_description_mix_level_db(float[] level);
     private native int native_set_dual_mono_mode(int dualMonoMode);
     private native int native_get_dual_mono_mode(int[] dualMonoMode);
+    private native void native_setLogSessionId(@NonNull String logSessionId);
+
+    /**
+     * Sets the audio service Player Interface Id.
+     *
+     * The playerIId does not change over the lifetime of the client
+     * Java AudioTrack and is set automatically on creation.
+     *
+     * This call informs the native AudioTrack for metrics logging purposes.
+     *
+     * @param id the value reported by AudioManager when registering the track.
+     *           A value of -1 indicates invalid - the playerIId was never set.
+     * @throws IllegalStateException if AudioTrack not initialized.
+     */
+    private native void native_setPlayerIId(int playerIId);
 
     //---------------------------------------------------------
     // Utility methods
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 71ee57e..0073b5c 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -351,9 +351,9 @@
     oneway void unregisterCommunicationDeviceDispatcher(
             ICommunicationDeviceDispatcher dispatcher);
 
-    boolean areFastScrollSoundEffectsEnabled();
+    boolean areNavigationRepeatSoundEffectsEnabled();
 
-    oneway void setFastScrollSoundEffectsEnabled(boolean enabled);
+    oneway void setNavigationRepeatSoundEffectsEnabled(boolean enabled);
 
     boolean isHomeSoundEffectEnabled();
 
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 4407efa..5d0f0aa 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -78,7 +78,7 @@
 
     private final int mImplType;
     // uniquely identifies the Player Interface throughout the system (P I Id)
-    private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
+    protected int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 
     @GuardedBy("mLock")
     private int mState;
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index ee0be01..952bbf5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -149,7 +149,7 @@
     /**
      * Invalid 64-bit filter ID.
      */
-    public static final long INVALID_FILTER_ID_64BIT =
+    public static final long INVALID_FILTER_ID_LONG =
             android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_FILTER_ID_64BIT;
     /**
      * Invalid frequency that is used as the default frontend frequency setting.
@@ -932,8 +932,8 @@
     public int connectFrontendToCiCam(int ciCamId) {
         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
                 "linkFrontendToCiCam")) {
-            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)
-                    && checkCiCamResource(ciCamId)) {
+            if (checkCiCamResource(ciCamId)
+                    && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
                 return nativeLinkCiCam(ciCamId);
             }
         }
@@ -978,7 +978,8 @@
     public int disconnectFrontendToCiCam(int ciCamId) {
         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
                 "unlinkFrontendToCiCam")) {
-            if (mFrontendCiCamHandle != null && mFrontendCiCamId == ciCamId) {
+            if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
+                    && mFrontendCiCamId == ciCamId) {
                 int result = nativeUnlinkCiCam(ciCamId);
                 if (result == RESULT_SUCCESS) {
                     mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
@@ -1409,6 +1410,7 @@
         boolean granted = mTunerResourceManager.requestCiCam(request, ciCamHandle);
         if (granted) {
             mFrontendCiCamHandle = ciCamHandle[0];
+            mFrontendCiCamId = ciCamId;
         }
         return granted;
     }
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 9f97b61..394211be 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntRange;
 import android.annotation.SystemApi;
 
 /**
@@ -51,6 +52,7 @@
     /**
      * Gets MPU sequence number of filtered data.
      */
+    @IntRange(from = 0)
     public int getMpuSequenceNumber() {
         return mMpuSequenceNumber;
     }
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 51b685a..2f3e2d8 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -309,7 +309,8 @@
     }
 
     /**
-     * Gets the filter Id.
+     * Gets the filter Id in 32-bit. For any Tuner SoC that supports 64-bit filter architecture,
+     * use {@link #getIdLong()}.
      */
     public int getId() {
         synchronized (mLock) {
@@ -319,9 +320,10 @@
     }
 
     /**
-     * Gets the 64-bit filter Id.
+     * Gets the 64-bit filter Id. For any Tuner SoC that supports 32-bit filter architecture,
+     * use {@link #getId()}.
      */
-    public long getId64Bit() {
+    public long getIdLong() {
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             return nativeGetId64Bit();
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 91be5c3..dbd85e9 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.filter;
 
 import android.annotation.BytesLong;
+import android.annotation.IntRange;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.media.MediaCodec.LinearBlock;
@@ -154,6 +155,7 @@
     /**
      * Gets MPU sequence number of filtered data.
      */
+    @IntRange(from = 0)
     public int getMpuSequenceNumber() {
         return mMpuSequenceNumber;
     }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 6a41c74..58a81d9 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner.filter;
 
 import android.annotation.BytesLong;
+import android.annotation.IntRange;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.filter.RecordSettings.ScHevcIndex;
 
@@ -69,6 +70,7 @@
      * {@link android.media.tv.tuner.TunerVersionChecker#getTunerVersion()} to get the version
      * information.
      */
+    @IntRange(from = 0)
     public int getMpuSequenceNumber() {
         return mMpuSequenceNumber;
     }
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
index 695e596..bfb7460 100644
--- a/media/java/android/media/tv/tuner/filter/PesEvent.java
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntRange;
 import android.annotation.SystemApi;
 
 /**
@@ -53,6 +54,7 @@
     /**
      * Gets MPU sequence number of filtered data.
      */
+    @IntRange(from = 0)
     public int getMpuSequenceNumber() {
         return mMpuSequenceNumber;
     }
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 73e1f7d..56f6c45 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -424,7 +424,7 @@
 static bool throwExceptionAsNecessary(
         JNIEnv *env, const sp<IDrm> &drm, status_t err, const char *msg = NULL) {
     std::string msgStr;
-    if (drm != NULL) {
+    if (drm != NULL && err != OK) {
         msgStr = DrmUtils::GetExceptionMessage(err, msg, drm);
         msg = msgStr.c_str();
     }
diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp
index 359ef36..78cb5b0 100644
--- a/media/jni/tuner/DemuxClient.cpp
+++ b/media/jni/tuner/DemuxClient.cpp
@@ -216,7 +216,7 @@
 Result DemuxClient::close() {
     if (mTunerDemux != NULL) {
         Status s = mTunerDemux->close();
-        mDemux = NULL;
+        mTunerDemux = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index bbabc28..736b8f9 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -267,9 +267,6 @@
     AidlMQ* mFilterMQ;
     EventFlag* mFilterMQEventFlag;
 
-    sp<FilterClientCallback> mCallback;
-    sp<HidlFilterCallback> mHidlCallback;
-
     native_handle_t* mAvSharedHandle;
     uint64_t mAvSharedMemSize;
     bool mIsMediaFilter;
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 9e36642..f400a71 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -78,8 +78,6 @@
 
 FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
     mTunerFrontend = tunerFrontend;
-    mAidlCallback = NULL;
-    mHidlCallback = NULL;
     mType = type;
 }
 
@@ -87,22 +85,21 @@
     mTunerFrontend = NULL;
     mFrontend = NULL;
     mFrontend_1_1 = NULL;
-    mAidlCallback = NULL;
-    mHidlCallback = NULL;
     mId = -1;
     mType = -1;
 }
 
 Result FrontendClient::setCallback(sp<FrontendClientCallback> frontendClientCallback) {
     if (mTunerFrontend != NULL) {
-        mAidlCallback = ::ndk::SharedRefBase::make<TunerFrontendCallback>(frontendClientCallback);
-        mAidlCallback->setFrontendType(mType);
-        Status s = mTunerFrontend->setCallback(mAidlCallback);
+        shared_ptr<TunerFrontendCallback> aidlCallback =
+                ::ndk::SharedRefBase::make<TunerFrontendCallback>(frontendClientCallback);
+        aidlCallback->setFrontendType(mType);
+        Status s = mTunerFrontend->setCallback(aidlCallback);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
-    mHidlCallback = new HidlFrontendCallback(frontendClientCallback);
-    return mFrontend->setCallback(mHidlCallback);
+    sp<HidlFrontendCallback> hidlCallback = new HidlFrontendCallback(frontendClientCallback);
+    return mFrontend->setCallback(hidlCallback);
 }
 
 void FrontendClient::setHidlFrontend(sp<IFrontend> frontend) {
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index f71616c..1dd950e 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -226,9 +226,6 @@
      */
     sp<::android::hardware::tv::tuner::V1_1::IFrontend> mFrontend_1_1;
 
-    shared_ptr<TunerFrontendCallback> mAidlCallback;
-    sp<HidlFrontendCallback> mHidlCallback;
-
     int mId;
     int mType;
 };
diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp
index 5b6e46e..073c49a 100644
--- a/media/jni/tuner/LnbClient.cpp
+++ b/media/jni/tuner/LnbClient.cpp
@@ -45,14 +45,15 @@
 
 Result LnbClient::setCallback(sp<LnbClientCallback> cb) {
     if (mTunerLnb != NULL) {
-        mAidlCallback = ::ndk::SharedRefBase::make<TunerLnbCallback>(cb);
-        Status s = mTunerLnb->setCallback(mAidlCallback);
+        shared_ptr<TunerLnbCallback> aidlCallback =
+                ::ndk::SharedRefBase::make<TunerLnbCallback>(cb);
+        Status s = mTunerLnb->setCallback(aidlCallback);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mLnb != NULL) {
-        mHidlCallback = new HidlLnbCallback(cb);
-        return mLnb->setCallback(mHidlCallback);
+        sp<HidlLnbCallback> hidlCallback = new HidlLnbCallback(cb);
+        return mLnb->setCallback(hidlCallback);
     }
 
     return Result::INVALID_STATE;
diff --git a/media/jni/tuner/LnbClient.h b/media/jni/tuner/LnbClient.h
index 465dc23..7c6118c 100644
--- a/media/jni/tuner/LnbClient.h
+++ b/media/jni/tuner/LnbClient.h
@@ -126,9 +126,6 @@
      */
     sp<ILnb> mLnb;
 
-    shared_ptr<TunerLnbCallback> mAidlCallback;
-    sp<HidlLnbCallback> mHidlCallback;
-
     LnbId mId;
 };
 }  // namespace android
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index cf17ed6..c9a7e83 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -49,8 +49,6 @@
     if (mTunerService == NULL) {
         ALOGE("Failed to get tuner service");
     } else {
-        // TODO: b/178124017 update TRM in TunerService independently.
-        mTunerService->updateTunerResources();
         mTunerService->getTunerHalVersion(&mTunerVersion);
     }
 }
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index e51add2..e8cf63f 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -27,14 +27,13 @@
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
 
-#include <ui/HdrCapabilities.h>
+#include <ui/DynamicDisplayInfo.h>
 
 #include <utils/Timers.h>
 
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
 using namespace android;
-using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;
 
 using Transaction = SurfaceComposerClient::Transaction;
 
@@ -72,14 +71,13 @@
         return false;
     }
 
-    HdrCapabilities hdrCapabilities;
-    status_t err = client->getHdrCapabilities(display, &hdrCapabilities);
-    if (err) {
+    ui::DynamicDisplayInfo info;
+    if (status_t err = client->getDynamicDisplayInfo(display, &info); err != NO_ERROR) {
         ALOGE("unable to get hdr capabilities");
-        return false;
+        return err;
     }
 
-    return !hdrCapabilities.getSupportedHdrTypes().empty();
+    return !info.hdrCapabilities.getSupportedHdrTypes().empty();
 }
 
 static bool isDataSpaceValid(const sp<SurfaceControl>& surfaceControl, ADataSpace dataSpace) {
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
index 19fa91f..0287b1f 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
@@ -20,27 +20,11 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingStart="20dp"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:background="?android:attr/selectableItemBackground"
     android:clipToPadding="false">
 
-    <LinearLayout
-        android:id="@+id/icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="56dp"
-        android:gravity="start|top"
-        android:orientation="horizontal"
-        android:paddingEnd="12dp"
-        android:paddingTop="16dp"
-        android:paddingBottom="4dp">
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-    </LinearLayout>
-
     <TextView
         android:id="@android:id/title"
         android:layout_width="wrap_content"
@@ -50,5 +34,5 @@
         android:clickable="false"
         android:longClickable="false"
         android:maxLines="10"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textAppearance="@style/TextAppearance.TopIntroText"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
new file mode 100644
index 0000000..e7eb9f4
--- /dev/null
+++ b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<resources>
+    <style name="TextAppearance.TopIntroText"
+           parent="@*android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3866151..79fbcc3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1007,6 +1007,9 @@
     <!-- Settings item title to select the default behavior for transcoding if an encodig is not supported by an app. [CHAR LIMIT=85] -->
     <string name="transcode_default">Assume apps support modern formats</string>
 
+    <!-- Settings item title to select whether to show transcoding notifications. [CHAR LIMIT=85] -->
+    <string name="transcode_notification">Show transcoding notifications</string>
+
     <!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
     <string name="runningservices_settings_title">Running services</string>
     <!-- Services settings screen, setting option summary for the user to go to the screen to view running services  -->
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 66165b6..ad6a531 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -147,5 +147,10 @@
         VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.ONE_HANDED_KEYGUARD_SIDE,
+                new InclusiveIntegerRangeValidator(
+                        /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT,
+                        /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 6719f17..d715832 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -283,6 +283,7 @@
                     Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
                     Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.FANCY_IME_ANIMATIONS,
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
                     Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
                     Settings.Global.FORCED_APP_STANDBY_ENABLED,
                     Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a15ceb6..2594840 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -86,6 +86,7 @@
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <!--  TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
     <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
+    <uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
     <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2faca8d..fbe58c5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -334,6 +334,11 @@
             </intent-filter>
         </receiver>
 
+        <activity android:name=".screenshot.LongScreenshotActivity"
+                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+                  android:process=":screenshot"
+                  android:finishOnTaskLaunch="true" />
+
         <activity android:name=".screenrecord.ScreenRecordDialog"
             android:theme="@style/ScreenRecord"
             android:showForAllUsers="true"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
index 7986809..71cdaf5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
@@ -24,7 +24,7 @@
     <include
         style="@style/BouncerSecurityContainer"
         layout="@layout/keyguard_host_view"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 </FrameLayout>
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index f3ec39d..b6a41c2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -64,7 +64,7 @@
         android:id="@+id/new_lockscreen_clock_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentEnd="true"
+        android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
         android:visibility="gone">
         <com.android.keyguard.AnimatableClockView
@@ -73,7 +73,7 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
             android:gravity="center_horizontal"
-            android:textSize="100dp"
+            android:textSize="60dp"
             android:fontFamily="@font/clock"
             android:typeface="monospace"
             android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index 04e645b..c75ee51 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -41,13 +41,14 @@
         android:layout_gravity="center">
         <com.android.keyguard.KeyguardSecurityViewFlipper
             android:id="@+id/view_flipper"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:clipChildren="false"
             android:clipToPadding="false"
             android:paddingTop="@dimen/keyguard_security_view_top_margin"
             android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
             android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
+            android:layout_gravity="center"
             android:gravity="center">
         </com.android.keyguard.KeyguardSecurityViewFlipper>
     </com.android.keyguard.KeyguardSecurityContainer>
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
similarity index 76%
rename from packages/SystemUI/res/drawable/toast_background.xml
rename to packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
index 5c45e83..e09bf7e 100644
--- a/packages/SystemUI/res/drawable/toast_background.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
@@ -14,8 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#FFFFFFFF" />
-    <corners android:radius="@dimen/toast_bg_radius" />
-</shape>
+
+<resources>
+    <bool name="can_use_one_handed_bouncer">true</bool>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index 8d9d6ee..6176f7c 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -22,4 +22,5 @@
 
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
+    <bool name="can_use_one_handed_bouncer">false</bool>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 2a5784a..115a156 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -47,26 +47,25 @@
     <dimen name="eca_overlap">-10dip</dimen>
 
     <!-- Slice header -->
-    <dimen name="widget_title_font_size">24dp</dimen>
-    <dimen name="header_subtitle_padding">12dp</dimen>
+    <dimen name="widget_title_font_size">20dp</dimen>
+    <dimen name="widget_title_line_height">24dp</dimen>
     <dimen name="header_icon_size">16dp</dimen>
     <!-- Slice subtitle -->
-    <dimen name="widget_label_font_size">18dp</dimen>
+    <dimen name="widget_label_font_size">16dp</dimen>
+    <dimen name="widget_label_line_height">20dp</dimen>
     <!-- Clock without header -->
     <dimen name="widget_big_font_size">54dp</dimen>
     <dimen name="bottom_text_spacing_digital">0dp</dimen>
     <dimen name="title_clock_padding">4dp</dimen>
     <!-- Clock with header -->
     <dimen name="widget_small_font_size">@dimen/widget_title_font_size</dimen>
-    <dimen name="widget_vertical_padding">17dp</dimen>
+    <dimen name="widget_vertical_padding">5dp</dimen>
     <dimen name="widget_vertical_padding_with_header">25dp</dimen>
     <dimen name="widget_vertical_padding_clock">12dp</dimen>
     <!-- Subtitle paddings -->
     <dimen name="widget_horizontal_padding">8dp</dimen>
-    <dimen name="widget_icon_size">20dp</dimen>
+    <dimen name="widget_icon_size">18dp</dimen>
     <dimen name="widget_icon_padding">8dp</dimen>
-    <dimen name="subtitle_clock_padding">0dp</dimen>
-    <dimen name="header_row_font_size">14dp</dimen>
     <!-- Notification shelf padding when dark -->
     <dimen name="widget_bottom_separator_padding">-6dp</dimen>
 
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2391803..8f42cbe 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -120,6 +120,7 @@
 
     <style name="TextAppearance.Keyguard">
         <item name="android:textSize">@dimen/widget_title_font_size</item>
+        <item name="android:lineHeight">@dimen/widget_title_line_height</item>
         <item name="android:gravity">center</item>
         <item name="android:ellipsize">end</item>
         <item name="android:maxLines">2</item>
@@ -133,6 +134,7 @@
         <item name="android:layout_height">wrap_content</item>
         <item name="android:lines">1</item>
         <item name="android:textSize">@dimen/widget_label_font_size</item>
+        <item name="android:lineHeight">@dimen/widget_label_line_height</item>
     </style>
 
     <style name="TextAppearance.Keyguard.BottomArea">
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a8ef7c3..23f3425 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -73,7 +73,7 @@
             android:id="@+id/qs_edge_guideline"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            systemui:layout_constraintGuide_percent="0.5"
+            systemui:layout_constraintGuide_percent="0.4"
             android:orientation="vertical"/>
 
         <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
index de4e062..ad558d8 100644
--- a/packages/SystemUI/res/layout/text_toast.xml
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -20,32 +20,30 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:maxWidth="@dimen/toast_width"
     android:orientation="horizontal"
-    android:background="@drawable/toast_background"
-    android:backgroundTint="?android:attr/colorBackground"
+    android:gravity="center_vertical"
+    android:maxWidth="@*android:dimen/toast_width"
+    android:background="@android:drawable/toast_frame"
     android:layout_marginEnd="16dp"
     android:layout_marginStart="16dp"
-    android:gravity="center_vertical">
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp">
 
-    <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
     <ImageView
         android:id="@+id/icon"
-        android:alpha="@dimen/toast_icon_alpha"
-        android:padding="11.5dp"
-        android:layout_width="@dimen/toast_icon_size"
-        android:layout_height="@dimen/toast_icon_size"/>
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:layout_marginEnd="10dp"/>
     <TextView
         android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:ellipsize="end"
         android:maxLines="2"
         android:paddingTop="12dp"
         android:paddingBottom="12dp"
-        android:paddingStart="0dp"
-        android:paddingEnd="22dp"
-        android:textSize="@dimen/toast_text_size"
-        android:textColor="?android:attr/textColorPrimary"
-        android:fontFamily="@*android:string/config_headlineFontFamily"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
+        android:lineHeight="20sp"
+        android:textAppearance="@*android:style/TextAppearance.Toast"/>
 </LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 24c7655..51d7b8e 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,6 +52,4 @@
     <!-- (footer_height -48dp)/2 -->
     <dimen name="controls_management_footer_top_margin">4dp</dimen>
     <dimen name="controls_management_favorites_top_margin">8dp</dimen>
-
-    <dimen name="toast_y_offset">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b07df9c..4bc5a30 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1359,11 +1359,4 @@
     <dimen name="rounded_slider_icon_size">24dp</dimen>
     <!-- rounded_slider_icon_size / 2 -->
     <dimen name="rounded_slider_icon_inset">12dp</dimen>
-
-    <dimen name="toast_width">296dp</dimen>
-    <item name="toast_icon_alpha" format="float" type="dimen">1</item>
-    <dimen name="toast_text_size">14sp</dimen>
-    <dimen name="toast_y_offset">48dp</dimen>
-    <dimen name="toast_icon_size">48dp</dimen>
-    <dimen name="toast_bg_radius">28dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3b42600..70e8b89 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2717,6 +2717,9 @@
     <!-- Controls dialog confirmation [CHAR LIMIT=30] -->
     <string name="controls_dialog_confirmation">Controls updated</string>
 
+    <!-- Controls tile secondary label when device is locked and user does not want access to controls from lockscreen [CHAR LIMIT=20] -->
+    <string name="controls_tile_locked">Device locked</string>
+
     <!-- Controls PIN entry dialog, switch to alphanumeric keyboard [CHAR LIMIT=100] -->
     <string name="controls_pin_use_alphanumeric">PIN contains letters or symbols</string>
     <!-- Controls PIN entry dialog, title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 0a117c1..1569fff 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -21,9 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.icu.text.NumberFormat;
-import android.util.MathUtils;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -94,11 +92,6 @@
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
-    float getClockTextTopPadding() {
-        Paint.FontMetrics fm = mView.getPaint().getFontMetrics();
-        return MathUtils.abs(fm.ascent - fm.top);
-    }
-
     /**
      * Updates the time for the view.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
index f01b67b..945c9c4 100644
--- a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
@@ -20,6 +20,7 @@
 
 import android.hardware.biometrics.BiometricSourceType;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 
@@ -73,13 +74,15 @@
 
     @Override
     protected void onViewAttached() {
-        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
-
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
         mIsKeyguardShowing = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
         mIsDozing = mStatusBarStateController.isDozing();
+        mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
         mAuthenticated = false;
+        updateButtonVisibility();
+
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
     }
 
     @Override
@@ -88,6 +91,15 @@
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
+    /**
+     * Call when this controller is no longer needed. This will remove the view from its parent.
+     */
+    public void destroy() {
+        if (mView != null && mView.getParent() != null) {
+            ((ViewGroup) mView.getParent()).removeView(mView);
+        }
+    }
+
     private void updateButtonVisibility() {
         mShowButton = !mAuthenticated && !mIsDozing && mIsKeyguardShowing
                 && !mIsBouncerShowing && !mRunningFPS;
@@ -143,6 +155,14 @@
                 public void onBiometricRunningStateChanged(boolean running,
                         BiometricSourceType biometricSourceType) {
                     mRunningFPS = running && biometricSourceType == FINGERPRINT;
+                    mAuthenticated &= !mRunningFPS;
+                    updateButtonVisibility();
+                }
+
+                @Override
+                public void onBiometricAuthenticated(int userId,
+                        BiometricSourceType biometricSourceType, boolean isStrongBiometric) {
+                    mAuthenticated = true;
                     updateButtonVisibility();
                 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 6eb54c2..93ed0ea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -179,20 +179,15 @@
             setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
             mSmallClockFrame.setVisibility(GONE);
             mNewLockscreenClockFrame.setVisibility(VISIBLE);
-
-            statusAreaLP.removeRule(RelativeLayout.BELOW);
+            statusAreaLP.addRule(RelativeLayout.BELOW, R.id.new_lockscreen_clock_view);
             statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
-            statusAreaLP.addRule(RelativeLayout.START_OF, R.id.new_lockscreen_clock_view);
-            statusAreaLP.width = 0;
         } else {
             setPaddingRelative(0, 0, 0, 0);
             mSmallClockFrame.setVisibility(VISIBLE);
             mNewLockscreenClockFrame.setVisibility(GONE);
 
             statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
-            statusAreaLP.removeRule(RelativeLayout.START_OF);
             statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
-            statusAreaLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
         }
 
         requestLayout();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e375877..0675200 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@
 import android.content.ContentResolver;
 import android.content.res.Resources;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.view.View;
 import android.view.ViewGroup;
@@ -212,10 +213,10 @@
      * keep the clock centered.
      */
     void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
-        x = Math.abs(x);
+        x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
         if (mNewLockScreenClockFrame != null) {
             PropertyAnimator.setProperty(mNewLockScreenClockFrame, AnimatableProperty.TRANSLATION_X,
-                    -x, props, animate);
+                    x, props, animate);
             PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_X,
                     scale, props, animate);
             PropertyAnimator.setProperty(mNewLockScreenLargeClockFrame, AnimatableProperty.SCALE_Y,
@@ -277,15 +278,6 @@
         refreshFormat(mTimeFormat);
     }
 
-    float getClockTextTopPadding() {
-        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1
-                && mNewLockScreenClockViewController != null) {
-            return mNewLockScreenClockViewController.getClockTextTopPadding();
-        }
-
-        return mView.getClockTextTopPadding();
-    }
-
     private void updateAodIcons() {
         NotificationIconContainer nic = (NotificationIconContainer)
                 mView.findViewById(
@@ -337,4 +329,8 @@
             sCacheKey = key;
         }
     }
+
+    private int getCurrentLayoutDirection() {
+        return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 92b65b2..533bec1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -136,14 +136,23 @@
 
     @Override
     public void startAppearAnimation() {
-        // Reset state, and let IME animation reveal the view as it slides in
+        // Reset state, and let IME animation reveal the view as it slides in, if one exists.
+        // It is possible for an IME to have no view, so provide a default animation since no
+        // calls to animateForIme would occur
         setAlpha(0f);
+        animate()
+            .alpha(1f)
+            .setDuration(500)
+            .setStartDelay(300)
+            .start();
+
         setTranslationY(0f);
     }
 
     @Override
     public void animateForIme(float interpolatedFraction) {
-        setAlpha(interpolatedFraction);
+        animate().cancel();
+        setAlpha(Math.max(interpolatedFraction, getAlpha()));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5f6fd30..f511ed1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -29,12 +29,17 @@
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.OrientationEventListener;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewPropertyAnimator;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimationControlListener;
@@ -55,6 +60,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.List;
 
@@ -99,6 +105,12 @@
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
 
+    private boolean mIsSecurityViewLeftAligned = true;
+    private boolean mOneHandedMode = false;
+    private SecurityMode mSecurityMode = SecurityMode.Invalid;
+    private ViewPropertyAnimator mRunningOneHandedAnimator;
+    private final OrientationEventListener mOrientationEventListener;
+
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
             new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
 
@@ -157,16 +169,20 @@
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
         boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
+
         void userActivity();
+
         void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
 
         /**
-         * @param strongAuth wheher the user has authenticated with strong authentication like
-         *                   pattern, password or PIN but not by trust agents or fingerprint
+         * @param strongAuth   wheher the user has authenticated with strong authentication like
+         *                     pattern, password or PIN but not by trust agents or fingerprint
          * @param targetUserId a user that needs to be the foreground user at the finish completion.
          */
         void finish(boolean strongAuth, int targetUserId);
+
         void reset();
+
         void onCancelClicked();
     }
 
@@ -224,12 +240,136 @@
         super(context, attrs, defStyle);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mViewConfiguration = ViewConfiguration.get(context);
+
+        mOrientationEventListener = new OrientationEventListener(context) {
+            @Override
+            public void onOrientationChanged(int orientation) {
+                updateLayoutForSecurityMode(mSecurityMode);
+            }
+        };
     }
 
     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
+        mSecurityMode = securityMode;
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
         updateBiometricRetry(securityMode, faceAuthEnabled);
 
+        updateLayoutForSecurityMode(securityMode);
+        mOrientationEventListener.enable();
+    }
+
+    void updateLayoutForSecurityMode(SecurityMode securityMode) {
+        mSecurityMode = securityMode;
+        mOneHandedMode = canUseOneHandedBouncer();
+
+        if (mOneHandedMode) {
+            mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
+        }
+
+        updateSecurityViewGravity();
+        updateSecurityViewLocation(false);
+    }
+
+    /** Return whether the one-handed keyguard should be enabled. */
+    private boolean canUseOneHandedBouncer() {
+        // Is it enabled?
+        if (!getResources().getBoolean(
+                com.android.internal.R.bool.config_enableOneHandedKeyguard)) {
+            return false;
+        }
+
+        if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
+            return false;
+        }
+
+        return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+    }
+
+    /** Read whether the one-handed keyguard should be on the left/right from settings. */
+    private boolean isOneHandedKeyguardLeftAligned(Context context) {
+        try {
+            return Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
+                    == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+        } catch (Settings.SettingNotFoundException ex) {
+            return true;
+        }
+    }
+
+    private void updateSecurityViewGravity() {
+        View securityView = findKeyguardSecurityView();
+
+        if (securityView == null) {
+            return;
+        }
+
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
+
+        if (mOneHandedMode) {
+            lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
+        } else {
+            lp.gravity = Gravity.CENTER_HORIZONTAL;
+        }
+
+        securityView.setLayoutParams(lp);
+    }
+
+    /**
+     * Moves the inner security view to the correct location (in one handed mode) with animation.
+     * This is triggered when the user taps on the side of the screen that is not currently occupied
+     * by the security view .
+     */
+    private void updateSecurityViewLocation(boolean animate) {
+        View securityView = findKeyguardSecurityView();
+
+        if (securityView == null) {
+            return;
+        }
+
+        if (!mOneHandedMode) {
+            securityView.setTranslationX(0);
+            return;
+        }
+
+        if (mRunningOneHandedAnimator != null) {
+            mRunningOneHandedAnimator.cancel();
+            mRunningOneHandedAnimator = null;
+        }
+
+        int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
+
+        if (animate) {
+            mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
+            mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+            mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRunningOneHandedAnimator = null;
+                }
+            });
+
+            mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mRunningOneHandedAnimator.start();
+        } else {
+            securityView.setTranslationX(targetTranslation);
+        }
+    }
+
+    @Nullable
+    private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+
+            if (isKeyguardSecurityView(child)) {
+                return (KeyguardSecurityViewFlipper) child;
+            }
+        }
+
+        return null;
+    }
+
+    private boolean isKeyguardSecurityView(View view) {
+        return view instanceof KeyguardSecurityViewFlipper;
     }
 
     public void onPause() {
@@ -238,6 +378,7 @@
             mAlertDialog = null;
         }
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
+        mOrientationEventListener.disable();
     }
 
     @Override
@@ -319,19 +460,44 @@
                 if (mSwipeListener != null) {
                     mSwipeListener.onSwipeUp();
                 }
+            } else {
+                if (!mIsDragging) {
+                    handleTap(event);
+                }
             }
         }
         return true;
     }
 
+    private void handleTap(MotionEvent event) {
+        // If we're using a fullscreen security mode, skip
+        if (!mOneHandedMode) {
+            return;
+        }
+
+        // Did the tap hit the "other" side of the bouncer?
+        if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f))
+                || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) {
+            mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
+
+            Settings.Global.putInt(
+                    mContext.getContentResolver(),
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+                    mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+                            : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+
+            updateSecurityViewLocation(true);
+        }
+    }
+
     void setSwipeListener(SwipeListener swipeListener) {
         mSwipeListener = swipeListener;
     }
 
     private void startSpringAnimation(float startVelocity) {
         mSpringAnimation
-            .setStartVelocity(startVelocity)
-            .animateToFinalPosition(0);
+                .setStartVelocity(startVelocity)
+                .animateToFinalPosition(0);
     }
 
     public void startDisappearAnimation(SecurityMode securitySelection) {
@@ -441,18 +607,17 @@
         return insets.inset(0, 0, 0, inset);
     }
 
-
     private void showDialog(String title, String message) {
         if (mAlertDialog != null) {
             mAlertDialog.dismiss();
         }
 
         mAlertDialog = new AlertDialog.Builder(mContext)
-            .setTitle(title)
-            .setMessage(message)
-            .setCancelable(false)
-            .setNeutralButton(R.string.ok, null)
-            .create();
+                .setTitle(title)
+                .setMessage(message)
+                .setCancelable(false)
+                .setNeutralButton(R.string.ok, null)
+                .create();
         if (!(mContext instanceof Activity)) {
             mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         }
@@ -490,6 +655,44 @@
         }
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int maxHeight = 0;
+        int maxWidth = 0;
+        int childState = 0;
+
+        int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                MeasureSpec.getSize(widthMeasureSpec) / 2,
+                MeasureSpec.getMode(widthMeasureSpec));
+
+        for (int i = 0; i < getChildCount(); i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() != GONE) {
+                if (mOneHandedMode && isKeyguardSecurityView(view)) {
+                    measureChildWithMargins(view, halfWidthMeasureSpec, 0,
+                            heightMeasureSpec, 0);
+                } else {
+                    measureChildWithMargins(view, widthMeasureSpec, 0,
+                            heightMeasureSpec, 0);
+                }
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                maxWidth = Math.max(maxWidth,
+                        view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+                maxHeight = Math.max(maxHeight,
+                        view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+                childState = combineMeasuredStates(childState, view.getMeasuredState());
+            }
+        }
+
+        // Check against our minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+                resolveSizeAndState(maxHeight, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
     void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
         String message = null;
         switch (userType) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1a8d420..fdab8db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -404,6 +404,7 @@
         if (newView != null) {
             newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
             mSecurityViewFlipperController.show(newView);
+            mView.updateLayoutForSecurityMode(securityMode);
         }
 
         mSecurityCallback.onSecurityModeChanged(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..631c248 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -92,4 +92,13 @@
                 throw new IllegalStateException("Unknown security quality:" + security);
         }
     }
+
+    /**
+     * Returns whether the given security view should be used in a "one handed" way. This can be
+     * used to change how the security view is drawn (e.g. take up less of the screen, and align to
+     * one side).
+     */
+    public static boolean isSecurityViewOneHanded(SecurityMode securityMode) {
+        return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index fb97a30..2373d75 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -85,10 +85,6 @@
      */
     private Runnable mContentChangeListener;
     private boolean mHasHeader;
-    private final int mRowWithHeaderPadding;
-    private final int mRowPadding;
-    private float mRowTextSize;
-    private float mRowWithHeaderTextSize;
     private View.OnClickListener mOnClickListener;
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@@ -97,9 +93,6 @@
         super(context, attrs);
 
         Resources resources = context.getResources();
-        mRowPadding = resources.getDimensionPixelSize(R.dimen.subtitle_clock_padding);
-        mRowWithHeaderPadding = resources.getDimensionPixelSize(R.dimen.header_subtitle_padding);
-
         mLayoutTransition = new LayoutTransition();
         mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2);
         mLayoutTransition.setDuration(LayoutTransition.APPEARING, DEFAULT_ANIM_DURATION);
@@ -120,10 +113,6 @@
         mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
         mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
         mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size);
-        mRowTextSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.widget_label_font_size);
-        mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.header_row_font_size);
         mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
     }
 
@@ -204,7 +193,6 @@
         LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
         layoutParams.gravity = mLockScreenMode !=  KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
                 ? Gravity.START :  Gravity.CENTER;
-        layoutParams.topMargin = mHasHeader ? mRowWithHeaderPadding : mRowPadding;
         mRow.setLayoutParams(layoutParams);
 
         for (int i = startIndex; i < subItemsCount; i++) {
@@ -230,8 +218,6 @@
             final SliceItem titleItem = rc.getTitleItem();
             button.setText(titleItem == null ? null : titleItem.getText());
             button.setContentDescription(rc.getContentDescription());
-            button.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                    mHasHeader ? mRowWithHeaderTextSize : mRowTextSize);
 
             Drawable iconDrawable = null;
             SliceItem icon = SliceQuery.find(item.getSlice(),
@@ -313,10 +299,6 @@
     void onDensityOrFontScaleChanged() {
         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
         mIconSizeWithHeader = (int) mContext.getResources().getDimension(R.dimen.header_icon_size);
-        mRowTextSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.widget_label_font_size);
-        mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.header_row_font_size);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6fb6760..934e768 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -266,15 +266,9 @@
             mKeyguardClockSwitchController.updateLockScreenMode(mode);
             mKeyguardSliceViewController.updateLockScreenMode(mode);
             if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
-                // align the top of keyguard_status_area with the top of the clock text instead
-                // of the top of the view
-                mKeyguardSliceViewController.updateTopMargin(
-                        mKeyguardClockSwitchController.getClockTextTopPadding());
                 mView.setCanShowOwnerInfo(false);
                 mView.setCanShowLogout(false);
             } else {
-                // reset margin
-                mKeyguardSliceViewController.updateTopMargin(0);
                 mView.setCanShowOwnerInfo(true);
                 mView.setCanShowLogout(false);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index d8ca639..9686c91f 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -473,7 +473,8 @@
 
     @Override
     public void onOpNoted(int code, int uid, String packageName,
-            @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+            String attributionTag, @AppOpsManager.OpFlags int flags,
+            @AppOpsManager.Mode int result) {
         if (DEBUG) {
             Log.w(TAG, "Noted op: " + code + " with result "
                     + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 3ea8140..3bf75d1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
 
@@ -33,6 +35,7 @@
 
     @NonNull protected final Context mContext;
     @NonNull protected final Drawable mFingerprintDrawable;
+    @Nullable private View mView;
 
     public UdfpsAnimation(@NonNull Context context) {
         mContext = context;
@@ -53,6 +56,10 @@
         mFingerprintDrawable.setAlpha(alpha);
     }
 
+    public void setAnimationView(UdfpsAnimationView view) {
+        mView = view;
+    }
+
     /**
      * @return The amount of padding that's needed on each side of the sensor, in pixels.
      */
@@ -66,4 +73,10 @@
     public int getPaddingY() {
         return 0;
     }
+
+    protected void postInvalidateView() {
+        if (mView != null) {
+            mView.postInvalidate();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
index 501de9d..8664e44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
@@ -23,7 +23,6 @@
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.util.MathUtils;
-import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -42,7 +41,6 @@
 
     private static final String TAG = "UdfpsAnimationKeyguard";
 
-    @NonNull private final View mParent;
     @NonNull private final Context mContext;
     private final int mMaxBurnInOffsetX;
     private final int mMaxBurnInOffsetY;
@@ -52,10 +50,9 @@
     private float mBurnInOffsetX;
     private float mBurnInOffsetY;
 
-    UdfpsAnimationKeyguard(@NonNull View parent, @NonNull Context context,
+    UdfpsAnimationKeyguard(@NonNull Context context,
             @NonNull StatusBarStateController statusBarStateController) {
         super(context);
-        mParent = parent;
         mContext = context;
 
         mMaxBurnInOffsetX = context.getResources()
@@ -73,10 +70,10 @@
                 mInterpolatedDarkAmount);
         mBurnInOffsetY = MathUtils.lerp(0f,
                 getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
-                        - 0.5f * mMaxBurnInOffsetY,
+                        - mMaxBurnInOffsetY,
                 mInterpolatedDarkAmount);
         updateColor();
-        mParent.postInvalidate();
+        postInvalidateView();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 41ea4d6..44122cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -74,7 +74,14 @@
     }
 
     void setAnimation(@Nullable UdfpsAnimation animation) {
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.setAnimationView(null);
+        }
+
         mUdfpsAnimation = animation;
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.setAnimationView(this);
+        }
     }
 
     void onSensorRectUpdated(@NonNull RectF sensorRect) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e7b08e7..6451ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -44,7 +44,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -324,7 +323,7 @@
             case IUdfpsOverlayController.REASON_ENROLL_ENROLLING:
                 return new UdfpsAnimationEnroll(mContext);
             case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
-                return new UdfpsAnimationKeyguard(mView, mContext, mStatusBarStateController);
+                return new UdfpsAnimationKeyguard(mContext, mStatusBarStateController);
             case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER:
                 return new UdfpsAnimationFpmOther(mContext);
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 00cb28b..6ffecdb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -113,6 +113,7 @@
 
     void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) {
         mAnimationView.setAnimation(animation);
+
         mEnrollHelper = enrollHelper;
 
         if (enrollHelper != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 36937d6..6b7a1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -91,7 +91,7 @@
     }
 
     /**
-     * @return true if controls are feature-enabled and have available services to serve controls
+     * @return true if controls are feature-enabled and the user has the setting enabled
      */
     fun isEnabled() = featureEnabled && lazyControlsController.get().available
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 2b362b9..8d2639d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.screenrecord.ScreenRecordDialog;
+import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
 import com.android.systemui.tuner.TunerActivity;
@@ -99,4 +100,10 @@
     @IntoMap
     @ClassKey(PeopleSpaceActivity.class)
     public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity);
+
+    /** Inject into LongScreenshotActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(LongScreenshotActivity.class)
+    public abstract Activity bindLongScreenshotActivity(LongScreenshotActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 8ab135c..4418696 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -32,6 +32,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.AlarmTimeout;
@@ -41,12 +42,15 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * The policy controlling doze.
  */
 @DozeScope
 public class DozeUi implements DozeMachine.Part, TunerService.Tunable {
-
+    // if enabled, calls dozeTimeTick() whenever the time changes:
+    private static final boolean BURN_IN_TESTING_ENABLED = false;
     private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
     private final Context mContext;
     private final DozeHost mHost;
@@ -57,16 +61,28 @@
     private final boolean mCanAnimateTransition;
     private final DozeParameters mDozeParameters;
     private final DozeLog mDozeLog;
+    private final Lazy<StatusBarStateController> mStatusBarStateController;
 
     private boolean mKeyguardShowing;
     private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
             new KeyguardUpdateMonitorCallback() {
-
                 @Override
                 public void onKeyguardVisibilityChanged(boolean showing) {
                     mKeyguardShowing = showing;
                     updateAnimateScreenOff();
                 }
+
+                @Override
+                public void onTimeChanged() {
+                    if (BURN_IN_TESTING_ENABLED && mStatusBarStateController != null
+                            && mStatusBarStateController.get().isDozing()) {
+                        // update whenever the time changes for manual burn in testing
+                        mHost.dozeTimeTick();
+
+                        // Keep wakelock until a frame has been pushed.
+                        mHandler.post(mWakeLock.wrap(() -> {}));
+                    }
+                }
             };
 
     private long mLastTimeTickElapsed = 0;
@@ -75,7 +91,8 @@
     public DozeUi(Context context, AlarmManager alarmManager,
             WakeLock wakeLock, DozeHost host, @Main Handler handler,
             DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DozeLog dozeLog, TunerService tunerService) {
+            DozeLog dozeLog, TunerService tunerService,
+            Lazy<StatusBarStateController> statusBarStateController) {
         mContext = context;
         mWakeLock = wakeLock;
         mHost = host;
@@ -85,8 +102,8 @@
         mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         mDozeLog = dozeLog;
-
         tunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON);
+        mStatusBarStateController = statusBarStateController;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 8110fda..16e5196 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -95,7 +95,6 @@
                     mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                         if (isTunerEnabled()) {
                             mTunerService.showResetRequest(
-                                    mUserTracker.getUserHandle(),
                                     () -> {
                                         // Relaunch settings so that the tuner disappears.
                                         startSettingsActivity();
@@ -103,7 +102,7 @@
                         } else {
                             Toast.makeText(getContext(), R.string.tuner_toast,
                                     Toast.LENGTH_LONG).show();
-                            mTunerService.setTunerEnabled(mUserTracker.getUserHandle(), true);
+                            mTunerService.setTunerEnabled(true);
                         }
                         startSettingsActivity();
 
@@ -238,6 +237,6 @@
     }
 
     private boolean isTunerEnabled() {
-        return mTunerService.isTunerEnabled(mUserTracker.getUserHandle());
+        return mTunerService.isTunerEnabled();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 4144591..3b9f5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE
+import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsDialog
 import com.android.systemui.dagger.qualifiers.Background
@@ -92,7 +92,7 @@
     override fun isAvailable(): Boolean {
         return featureFlags.isKeyguardLayoutEnabled &&
                 controlsLockscreen &&
-                controlsComponent.getVisibility() != UNAVAILABLE
+                controlsComponent.getControlsController().isPresent
     }
 
     override fun newTileState(): QSTile.State {
@@ -119,7 +119,7 @@
     }
 
     override fun handleClick() {
-        if (state.state != Tile.STATE_UNAVAILABLE) {
+        if (state.state == Tile.STATE_ACTIVE) {
             mUiHandler.post {
                 createDialog()
                 controlsDialog?.show(controlsComponent.getControlsUiController().get())
@@ -129,15 +129,21 @@
 
     override fun handleUpdateState(state: QSTile.State, arg: Any?) {
         state.label = tileLabel
-        state.secondaryLabel = ""
-        state.stateDescription = ""
+
         state.contentDescription = state.label
         state.icon = icon
-        if (hasControlsApps.get()) {
-            state.state = Tile.STATE_ACTIVE
+        if (controlsComponent.isEnabled() && hasControlsApps.get()) {
             if (controlsDialog == null) {
                 mUiHandler.post(this::createDialog)
             }
+            if (controlsComponent.getVisibility() == AVAILABLE) {
+                state.state = Tile.STATE_ACTIVE
+                state.secondaryLabel = ""
+            } else {
+                state.state = Tile.STATE_INACTIVE
+                state.secondaryLabel = mContext.getText(R.string.controls_tile_locked)
+            }
+            state.stateDescription = state.secondaryLabel
         } else {
             state.state = Tile.STATE_UNAVAILABLE
             dismissDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
new file mode 100644
index 0000000..a6433ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * LongScreenshotActivity acquires bitmap data for a long screenshot and lets the user trim the top
+ * and bottom before saving/sharing/editing.
+ */
+public class LongScreenshotActivity extends Activity {
+    private static final String TAG = "LongScreenshotActivity";
+
+    private final UiEventLogger mUiEventLogger;
+    private final ScrollCaptureController mScrollCaptureController;
+
+    private ImageView mPreview;
+    private View mSave;
+    private View mCancel;
+    private View mEdit;
+    private View mShare;
+    private CropView mCropView;
+    private MagnifierView mMagnifierView;
+
+    private enum PendingAction {
+        SHARE,
+        EDIT,
+        SAVE
+    }
+
+    @Inject
+    public LongScreenshotActivity(UiEventLogger uiEventLogger,
+            ImageExporter imageExporter,
+            @Main Executor mainExecutor,
+            @Background Executor bgExecutor,
+            Context context) {
+        mUiEventLogger = uiEventLogger;
+
+        mScrollCaptureController = new ScrollCaptureController(context,
+                ScreenshotController.sScrollConnection, mainExecutor, bgExecutor, imageExporter);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.long_screenshot);
+
+        mPreview = findViewById(R.id.preview);
+        mSave = findViewById(R.id.save);
+        mCancel = findViewById(R.id.cancel);
+        mEdit = findViewById(R.id.edit);
+        mShare = findViewById(R.id.share);
+        mCropView = findViewById(R.id.crop_view);
+        mMagnifierView = findViewById(R.id.magnifier);
+        mCropView.setCropInteractionListener(mMagnifierView);
+
+        mSave.setOnClickListener(this::onClicked);
+        mCancel.setOnClickListener(this::onClicked);
+        mEdit.setOnClickListener(this::onClicked);
+        mShare.setOnClickListener(this::onClicked);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mPreview.getDrawable() == null) {
+            doCapture();
+        }
+    }
+
+    private void disableButtons() {
+        mSave.setEnabled(false);
+        mCancel.setEnabled(false);
+        mEdit.setEnabled(false);
+        mShare.setEnabled(false);
+    }
+
+    private void doEdit(Uri uri) {
+        String editorPackage = getString(R.string.config_screenshotEditor);
+        Intent intent = new Intent(Intent.ACTION_EDIT);
+        if (!TextUtils.isEmpty(editorPackage)) {
+            intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+        }
+        intent.setType("image/png");
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        startActivityAsUser(intent, UserHandle.CURRENT);
+        finishAndRemoveTask();
+    }
+
+    private void doShare(Uri uri) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("image/png");
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        Intent sharingChooserIntent = Intent.createChooser(intent, null)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+    }
+
+    private void onClicked(View v) {
+        int id = v.getId();
+        v.setPressed(true);
+        disableButtons();
+        if (id == R.id.save) {
+            startExport(PendingAction.SAVE);
+        } else if (id == R.id.cancel) {
+            finishAndRemoveTask();
+        } else if (id == R.id.edit) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
+            startExport(PendingAction.EDIT);
+        } else if (id == R.id.share) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
+            startExport(PendingAction.SHARE);
+        }
+    }
+
+    private void startExport(PendingAction action) {
+        mScrollCaptureController.startExport(mCropView.getTopBoundary(),
+                mCropView.getBottomBoundary(), new ScrollCaptureController.ExportCallback() {
+                    @Override
+                    public void onError() {
+                        Log.e(TAG, "Error exporting image data.");
+                    }
+
+                    @Override
+                    public void onExportComplete(Uri outputUri) {
+                        switch (action) {
+                            case EDIT:
+                                doEdit(outputUri);
+                                break;
+                            case SHARE:
+                                doShare(outputUri);
+                                break;
+                            case SAVE:
+                                // Nothing more to do
+                                finishAndRemoveTask();
+                                break;
+                        }
+                    }
+                });
+    }
+
+    private void doCapture() {
+        mScrollCaptureController.start(new ScrollCaptureController.ScrollCaptureCallback() {
+            @Override
+            public void onError() {
+                Log.e(TAG, "Error!");
+                finishAndRemoveTask();
+            }
+
+            @Override
+            public void onComplete(ImageTileSet imageTileSet) {
+                Log.i(TAG, "Got tiles " + imageTileSet.getWidth() + " x "
+                        + imageTileSet.getHeight());
+                mPreview.setImageDrawable(imageTileSet.getDrawable());
+                mMagnifierView.setImageTileset(imageTileSet);
+                mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
+            }
+        });
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 953b40b..31c693b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -41,6 +41,7 @@
 import android.app.WindowContext;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
@@ -100,6 +101,8 @@
 public class ScreenshotController {
     private static final String TAG = logTag(ScreenshotController.class);
 
+    public static ScrollCaptureClient.Connection sScrollConnection;
+
     /**
      * POD used in the AsyncTask which saves an image in the background.
      */
@@ -597,21 +600,12 @@
     }
 
     private void runScrollCapture(ScrollCaptureClient.Connection connection) {
-        cancelTimeout();
-        ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
-                mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger);
-        controller.attach(mWindow);
-        controller.start(new TakeScreenshotService.RequestCallback() {
-            @Override
-            public void reportError() {
-            }
+        sScrollConnection = connection;  // For LongScreenshotActivity to pick up.
 
-            @Override
-            public void onFinish() {
-                Log.d(TAG, "onFinish from ScrollCaptureController");
-                finishDismiss();
-            }
-        });
+        Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        mContext.startActivity(intent);
+        dismissScreenshot(false);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index ad5e637..863116a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,29 +16,16 @@
 
 package com.android.systemui.screenshot;
 
-import android.annotation.IdRes;
 import android.annotation.UiThread;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
-import android.view.View;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.Window;
-import android.widget.ImageView;
 
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.R;
 import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
 import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -50,7 +37,7 @@
 /**
  * Interaction controller between the UI and ScrollCaptureClient.
  */
-public class ScrollCaptureController implements OnComputeInternalInsetsListener {
+public class ScrollCaptureController {
     private static final String TAG = "ScrollCaptureController";
     private static final float MAX_PAGES_DEFAULT = 3f;
 
@@ -64,13 +51,6 @@
     private boolean mAtTopEdge;
     private Session mSession;
 
-    // TODO: Support saving without additional action.
-    private enum PendingAction {
-        SHARE,
-        EDIT,
-        SAVE
-    }
-
     public static final int MAX_HEIGHT = 12000;
 
     private final Connection mConnection;
@@ -80,172 +60,59 @@
     private final Executor mBgExecutor;
     private final ImageExporter mImageExporter;
     private final ImageTileSet mImageTileSet;
-    private final UiEventLogger mUiEventLogger;
 
     private ZonedDateTime mCaptureTime;
     private UUID mRequestId;
-    private RequestCallback mCallback;
-    private Window mWindow;
-    private ImageView mPreview;
-    private View mSave;
-    private View mCancel;
-    private View mEdit;
-    private View mShare;
-    private CropView mCropView;
-    private MagnifierView mMagnifierView;
+    private ScrollCaptureCallback mCaptureCallback;
 
     public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
-            Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
+            Executor bgExecutor, ImageExporter exporter) {
         mContext = context;
         mConnection = connection;
         mUiExecutor = uiExecutor;
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
-        mUiEventLogger = uiEventLogger;
         mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
     }
 
     /**
-     * @param window the window to display the preview
-     */
-    public void attach(Window window) {
-        mWindow = window;
-    }
-
-    /**
      * Run scroll capture!
      *
      * @param callback request callback to report back to the service
      */
-    public void start(RequestCallback callback) {
+    public void start(ScrollCaptureCallback callback) {
         mCaptureTime = ZonedDateTime.now();
         mRequestId = UUID.randomUUID();
-        mCallback = callback;
-
-        setContentView(R.layout.long_screenshot);
-        mWindow.getDecorView().getViewTreeObserver()
-                .addOnComputeInternalInsetsListener(this);
-        mPreview = findViewById(R.id.preview);
-
-        mSave = findViewById(R.id.save);
-        mCancel = findViewById(R.id.cancel);
-        mEdit = findViewById(R.id.edit);
-        mShare = findViewById(R.id.share);
-        mCropView = findViewById(R.id.crop_view);
-        mMagnifierView = findViewById(R.id.magnifier);
-        mCropView.setCropInteractionListener(mMagnifierView);
-
-        mSave.setOnClickListener(this::onClicked);
-        mCancel.setOnClickListener(this::onClicked);
-        mEdit.setOnClickListener(this::onClicked);
-        mShare.setOnClickListener(this::onClicked);
+        mCaptureCallback = callback;
 
         float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
                 SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
         mConnection.start(this::startCapture, maxPages);
     }
 
-
-    /** Ensure the entire window is touchable */
-    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
-        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-    }
-
-    void disableButtons() {
-        mSave.setEnabled(false);
-        mCancel.setEnabled(false);
-        mEdit.setEnabled(false);
-        mShare.setEnabled(false);
-    }
-
-    private void onClicked(View v) {
-        Log.d(TAG, "button clicked!");
-
-        int id = v.getId();
-        v.setPressed(true);
-        disableButtons();
-        if (id == R.id.save) {
-            startExport(PendingAction.SAVE);
-        } else if (id == R.id.cancel) {
-            doFinish();
-        } else if (id == R.id.edit) {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
-            startExport(PendingAction.EDIT);
-        } else if (id == R.id.share) {
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
-            startExport(PendingAction.SHARE);
-        }
-    }
-
-    private void doFinish() {
-        mPreview.setImageDrawable(null);
-        mMagnifierView.setImageTileset(null);
-        mImageTileSet.clear();
-        mCallback.onFinish();
-        mWindow.getDecorView().getViewTreeObserver()
-                .removeOnComputeInternalInsetsListener(this);
-    }
-
-    private void startExport(PendingAction action) {
+    /**
+     * @param topCrop    [0,1) fraction of the top of the image to be cropped out.
+     * @param bottomCrop (0, 1] fraction to be cropped out, e.g. 0.7 will crop out the bottom 30%.
+     */
+    public void startExport(float topCrop, float bottomCrop, ExportCallback callback) {
         Rect croppedPortion = new Rect(
                 0,
-                (int) (mImageTileSet.getHeight() * mCropView.getTopBoundary()),
+                (int) (mImageTileSet.getHeight() * topCrop),
                 mImageTileSet.getWidth(),
-                (int) (mImageTileSet.getHeight() * mCropView.getBottomBoundary()));
+                (int) (mImageTileSet.getHeight() * bottomCrop));
         ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
                 mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime);
         exportFuture.addListener(() -> {
             try {
                 ImageExporter.Result result = exportFuture.get();
-                if (action == PendingAction.EDIT) {
-                    doEdit(result.uri);
-                } else if (action == PendingAction.SHARE) {
-                    doShare(result.uri);
-                }
-                doFinish();
+                callback.onExportComplete(result.uri);
             } catch (InterruptedException | ExecutionException e) {
                 Log.e(TAG, "failed to export", e);
-                mCallback.onFinish();
+                callback.onError();
             }
         }, mUiExecutor);
     }
 
-    private void doEdit(Uri uri) {
-        String editorPackage = mContext.getString(R.string.config_screenshotEditor);
-        Intent intent = new Intent(Intent.ACTION_EDIT);
-        if (!TextUtils.isEmpty(editorPackage)) {
-            intent.setComponent(ComponentName.unflattenFromString(editorPackage));
-        }
-        intent.setType("image/png");
-        intent.setData(uri);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
-                | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-    }
-
-    private void doShare(Uri uri) {
-        Intent intent = new Intent(Intent.ACTION_SEND);
-        intent.setType("image/png");
-        intent.setData(uri);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
-                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        Intent sharingChooserIntent = Intent.createChooser(intent, null)
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-        mContext.startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
-    }
-
-    private void setContentView(@IdRes int id) {
-        mWindow.setContentView(id);
-    }
-
-    <T extends View> T findViewById(@IdRes int res) {
-        return mWindow.findViewById(res);
-    }
-
-
     private void onCaptureResult(CaptureResult result) {
         Log.d(TAG, "onCaptureResult: " + result);
         boolean emptyResult = result.captured.height() == 0;
@@ -327,11 +194,26 @@
         Log.d(TAG, "afterCaptureComplete");
 
         if (mImageTileSet.isEmpty()) {
-            session.end(mCallback::onFinish);
+            mCaptureCallback.onError();
         } else {
-            mPreview.setImageDrawable(mImageTileSet.getDrawable());
-            mMagnifierView.setImageTileset(mImageTileSet);
-            mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
+            mCaptureCallback.onComplete(mImageTileSet);
         }
     }
+
+    /**
+     * Callback for image capture completion or error.
+     */
+    public interface ScrollCaptureCallback {
+        void onComplete(ImageTileSet imageTileSet);
+        void onError();
+    }
+
+    /**
+     * Callback for image export completion or error.
+     */
+    public interface ExportCallback {
+        void onExportComplete(Uri outputUri);
+        void onError();
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 20efa32..d2ddd21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -1002,10 +1002,6 @@
         return false;
     }
 
-    public void onUiModeChanged() {
-        updateBackgroundColors();
-    }
-
     public void setController(NotificationShelfController notificationShelfController) {
         mController = notificationShelfController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 73c7fd1..f21771a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -128,15 +128,6 @@
             // this is a foreground-service disclosure for a user that does not need to show one
             return true;
         }
-        if (getFsc().isSystemAlertNotification(sbn)) {
-            final String[] apps = sbn.getNotification().extras.getStringArray(
-                    Notification.EXTRA_FOREGROUND_APPS);
-            if (apps != null && apps.length >= 1) {
-                if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
-                    return true;
-                }
-            }
-        }
 
         if (mIsMediaFlagEnabled && isMediaNotification(sbn)) {
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 998ae9e..3a87f68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -95,18 +95,6 @@
                             sbn.getUser().getIdentifier())) {
                 return true;
             }
-
-            // Filters out system alert notifications when unneeded
-            if (mForegroundServiceController.isSystemAlertNotification(sbn)) {
-                final String[] apps = sbn.getNotification().extras.getStringArray(
-                        Notification.EXTRA_FOREGROUND_APPS);
-                if (apps != null && apps.length >= 1) {
-                    if (!mForegroundServiceController.isSystemAlertWarningNeeded(
-                            sbn.getUser().getIdentifier(), apps[0])) {
-                        return true;
-                    }
-                }
-            }
             return false;
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 724921b..31d052d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -191,7 +191,10 @@
         initDimens();
     }
 
-    protected void updateBackgroundColors() {
+    /**
+     * Reload background colors from resources and invalidate views.
+     */
+    public void updateBackgroundColors() {
         updateColors();
         initBackground();
         updateBackgroundTint();
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 2c7c5cc..417ff5e 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
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -83,6 +84,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
@@ -453,6 +455,7 @@
     private NotificationEntry mTopHeadsUpEntry;
     private long mNumHeadsUp;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
+    private final FeatureFlags mFeatureFlags;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -492,8 +495,8 @@
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
             SysuiStatusBarStateController statusbarStateController,
-            AmbientState ambientState
-    ) {
+            AmbientState ambientState,
+            FeatureFlags featureFlags) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
         mSectionsManager = notificationSectionsManager;
@@ -530,6 +533,7 @@
         mGroupMembershipManager = groupMembershipManager;
         mGroupExpansionManager = groupExpansionManager;
         mStatusbarStateController = statusbarStateController;
+        mFeatureFlags = featureFlags;
     }
 
     void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
@@ -622,7 +626,12 @@
         mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
                 .getDefaultColor();
         updateBackgroundDimming();
-        mShelf.onUiModeChanged();
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child instanceof ActivatableNotificationView) {
+                ((ActivatableNotificationView) child).updateBackgroundColors();
+            }
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
@@ -1156,8 +1165,13 @@
                 if (stackStartPosition <= stackEndPosition) {
                     stackHeight = stackEndPosition;
                 } else {
-                    stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
-                            stackEndPosition, mQsExpansionFraction);
+                    if (shouldUseSplitNotificationShade(mFeatureFlags, getResources())) {
+                        // This prevents notifications from being collapsed when QS is expanded.
+                        stackHeight = (int) height;
+                    } else {
+                        stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
+                                stackEndPosition, mQsExpansionFraction);
+                    }
                 }
             } else {
                 stackHeight = (int) height;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a516742..3997028 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -223,6 +223,7 @@
             updateShowEmptyShadeView();
             mView.updateCornerRadius();
             mView.updateBgColor();
+            mView.updateDecorViews();
             mView.reinflateViews();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8cdaa63..f4830fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -134,7 +134,7 @@
         mStackScrollerController = stackScrollerController;
         mNotificationPanelViewController = notificationPanelViewController;
         notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
+        notificationPanelViewController.setVerticalTranslationListener(mUpdatePanelTranslation);
         notificationPanelViewController.setHeadsUpAppearanceController(this);
         mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScrollerController.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
@@ -171,7 +171,7 @@
         mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
         mWakeUpCoordinator.removeListener(this);
         mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
+        mNotificationPanelViewController.setVerticalTranslationListener(null);
         mNotificationPanelViewController.setHeadsUpAppearanceController(null);
         mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScrollerController.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a6daed5..2ce4037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -344,8 +344,11 @@
     }
 
     private float burnInPreventionOffsetX() {
-        return getBurnInOffset(mBurnInPreventionOffsetX * 2, true /* xAxis */)
-                - mBurnInPreventionOffsetX;
+        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+            return getBurnInOffset(mBurnInPreventionOffsetX * 2, true /* xAxis */)
+                    - mBurnInPreventionOffsetX;
+        }
+        return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */);
     }
 
     public static class Result {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 0b3fd16..6940050 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -196,7 +196,8 @@
             new MyOnHeadsUpChangedListener();
     private final HeightListener mHeightListener = new HeightListener();
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
-    private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
+    @VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
+            new StatusBarStateListener();
     private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
     private final BiometricUnlockController mBiometricUnlockController;
     private final NotificationPanelView mView;
@@ -280,18 +281,8 @@
 
                 @Override
                 public void onKeyguardVisibilityChanged(boolean showing) {
-                    if (mDisabledUdfpsController == null
-                            && mAuthController.getUdfpsRegion() != null
-                            && mAuthController.isUdfpsEnrolled(
-                                   KeyguardUpdateMonitor.getCurrentUser())) {
-                        mLayoutInflater.inflate(R.layout.disabled_udfps_view, mView);
-                        mDisabledUdfpsController = new DisabledUdfpsController(
-                                mView.findViewById(R.id.disabled_udfps_view),
-                                mStatusBarStateController,
-                                mUpdateMonitor,
-                                mAuthController,
-                                mStatusBarKeyguardViewManager);
-                        mDisabledUdfpsController.init();
+                    if (showing) {
+                        updateDisabledUdfpsController();
                     }
                 }
     };
@@ -473,7 +464,7 @@
     private ArrayList<Consumer<ExpandableNotificationRow>>
             mTrackingHeadsUpListeners =
             new ArrayList<>();
-    private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
+    private Runnable mVerticalTranslationListener;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
 
     private int mPanelAlpha;
@@ -867,25 +858,16 @@
 
     public void updateResources() {
         int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
-        ViewGroup.LayoutParams lp = mQsFrame.getLayoutParams();
-        if (lp.width != qsWidth) {
-            lp.width = qsWidth;
-            mQsFrame.setLayoutParams(lp);
-        }
-
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
-        lp = mNotificationStackScrollLayoutController.getLayoutParams();
-        if (lp.width != panelWidth) {
-            lp.width = panelWidth;
-            mNotificationStackScrollLayoutController.setLayoutParams(lp);
-        }
 
-        // In order to change the constraints at runtime, all children of the Constraint Layout
-        // must have ids.
+        // To change the constraints at runtime, all children of the ConstraintLayout must have ids
         ensureAllViewsHaveIds(mNotificationContainerParent);
         ConstraintSet constraintSet = new ConstraintSet();
         constraintSet.clone(mNotificationContainerParent);
         if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+            // width = 0 to take up all available space within constraints
+            qsWidth = 0;
+            panelWidth = 0;
             constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
             constraintSet.connect(
                     R.id.notification_stack_scroller, START,
@@ -894,6 +876,8 @@
             constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
             constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
         }
+        constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
+        constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
         constraintSet.applyTo(mNotificationContainerParent);
     }
 
@@ -1852,7 +1836,7 @@
         }
     }
 
-    private void setQsExpanded(boolean expanded) {
+    @VisibleForTesting void setQsExpanded(boolean expanded) {
         boolean changed = mQsExpanded != expanded;
         if (changed) {
             mQsExpanded = expanded;
@@ -1955,8 +1939,10 @@
     private void updateQsState() {
         mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
         mNotificationStackScrollLayoutController.setScrollingEnabled(
-                mBarState != KEYGUARD && (!mQsExpanded
-                        || mQsExpansionFromOverscroll));
+                mBarState != KEYGUARD
+                        && (!mQsExpanded
+                            || mQsExpansionFromOverscroll
+                            || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)));
 
         if (mKeyguardUserSwitcherController != null && mQsExpanded
                 && !mStackScrollerOverscrolling) {
@@ -2236,13 +2222,16 @@
 
     @Override
     protected boolean canCollapsePanelOnTouch() {
-        if (!isInSettings()) {
-            return mBarState == KEYGUARD
-                    || mIsPanelCollapseOnQQS
-                    || mNotificationStackScrollLayoutController.isScrolledToBottom();
-        } else {
+        if (!isInSettings() && mBarState == KEYGUARD) {
             return true;
         }
+
+        if (mNotificationStackScrollLayoutController.isScrolledToBottom()) {
+            return true;
+        }
+
+        return !Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)
+                && (isInSettings() || mIsPanelCollapseOnQQS);
     }
 
     @Override
@@ -2920,7 +2909,8 @@
      * @param x the x-coordinate the touch event
      */
     protected void updateHorizontalPanelPosition(float x) {
-        if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
+        if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()
+                || Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
             resetHorizontalPanelPosition();
             return;
         }
@@ -2948,9 +2938,8 @@
     protected void setHorizontalPanelTranslation(float translation) {
         mNotificationStackScrollLayoutController.setTranslationX(translation);
         mQsFrame.setTranslationX(translation);
-        int size = mVerticalTranslationListener.size();
-        for (int i = 0; i < size; i++) {
-            mVerticalTranslationListener.get(i).run();
+        if (mVerticalTranslationListener != null) {
+            mVerticalTranslationListener.run();
         }
     }
 
@@ -3243,12 +3232,8 @@
         mTrackingHeadsUpListeners.remove(listener);
     }
 
-    public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.add(verticalTranslationListener);
-    }
-
-    public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.remove(verticalTranslationListener);
+    public void setVerticalTranslationListener(Runnable verticalTranslationListener) {
+        mVerticalTranslationListener = verticalTranslationListener;
     }
 
     public void setHeadsUpAppearanceController(
@@ -3566,6 +3551,25 @@
         }
     }
 
+    private void updateDisabledUdfpsController() {
+        final boolean udfpsEnrolled = mAuthController.getUdfpsRegion() != null
+                && mAuthController.isUdfpsEnrolled(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (mDisabledUdfpsController == null && udfpsEnrolled) {
+            mLayoutInflater.inflate(R.layout.disabled_udfps_view, mView);
+            mDisabledUdfpsController = new DisabledUdfpsController(
+                    mView.findViewById(R.id.disabled_udfps_view),
+                    mStatusBarStateController,
+                    mUpdateMonitor,
+                    mAuthController,
+                    mStatusBarKeyguardViewManager);
+            mDisabledUdfpsController.init();
+        } else if (mDisabledUdfpsController != null && !udfpsEnrolled) {
+            mDisabledUdfpsController.destroy();
+            mDisabledUdfpsController = null;
+        }
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3615,6 +3619,10 @@
             NotificationStackScrollLayout.OnOverscrollTopChangedListener {
         @Override
         public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
+            // When in split shade, overscroll shouldn't carry through to QS
+            if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+                return;
+            }
             cancelQsAnimation();
             if (!mQsExpansionEnabled) {
                 amount = 0f;
@@ -3981,6 +3989,7 @@
             FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
             mStatusBarStateController.addCallback(mStatusBarStateListener);
             mConfigurationController.addCallback(mConfigurationListener);
+            updateDisabledUdfpsController();
             mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
             // Theme might have changed between inflating this view and attaching it to the
             // window, so
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index e394ebc..0c9ed66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -18,14 +18,12 @@
 
 import android.app.Fragment;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
-import androidx.annotation.DimenRes;
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.systemui.R;
@@ -83,22 +81,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        reloadWidth(mQsFrame, R.dimen.qs_panel_width);
-        reloadWidth(mStackScroller, R.dimen.notification_panel_width);
-    }
-
-    /**
-     * Loads the given width resource and sets it on the given View.
-     */
-    private void reloadWidth(View view, @DimenRes int width) {
-        LayoutParams params = (LayoutParams) view.getLayoutParams();
-        params.width = getResources().getDimensionPixelSize(width);
-        view.setLayoutParams(params);
-    }
-
-    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mBottomPadding = insets.getStableInsetBottom();
         setPadding(0, 0, 0, mBottomPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 365cd2a..fab1655 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -187,10 +187,7 @@
             mPluginToast.onOrientationChange(orientation);
         }
 
-        mDefaultY = mContext.getResources().getDimensionPixelSize(
-                mToastStyleEnabled
-                        ? com.android.systemui.R.dimen.toast_y_offset
-                        : R.dimen.toast_y_offset);
+        mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
         mDefaultGravity =
                 mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 78341ed..5b66216 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -43,11 +43,13 @@
     private static final String TAG_TUNER = "tuner";
 
     private final DemoModeController mDemoModeController;
+    private final TunerService mTunerService;
 
     @Inject
-    TunerActivity(DemoModeController demoModeController) {
+    TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
         super();
         mDemoModeController = demoModeController;
+        mTunerService = tunerService;
     }
 
     protected void onCreate(Bundle savedInstanceState) {
@@ -67,7 +69,7 @@
                     "com.android.settings.action.DEMO_MODE");
             final PreferenceFragment fragment = showDemoMode
                     ? new DemoModeFragment(mDemoModeController)
-                    : new TunerFragment();
+                    : new TunerFragment(mTunerService);
             getFragmentManager().beginTransaction().replace(R.id.content_frame,
                     fragment, TAG_TUNER).commit();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 4c724ae..989462a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.tuner;
 
-import android.app.ActivityManager;
+import android.annotation.SuppressLint;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -23,7 +23,6 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -56,6 +55,15 @@
 
     private static final int MENU_REMOVE = Menu.FIRST + 1;
 
+    private final TunerService mTunerService;
+
+    // We are the only ones who ever call this constructor, so don't worry about the warning
+    @SuppressLint("ValidFragment")
+    public TunerFragment(TunerService tunerService) {
+        super();
+        mTunerService = tunerService;
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -124,13 +132,9 @@
                 getActivity().finish();
                 return true;
             case MENU_REMOVE:
-                UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
-                TunerService.showResetRequest(getContext(), user, new Runnable() {
-                    @Override
-                    public void run() {
-                        if (getActivity() != null) {
-                            getActivity().finish();
-                        }
+                mTunerService.showResetRequest(() -> {
+                    if (getActivity() != null) {
+                        getActivity().finish();
                     }
                 });
                 return true;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index b67574d..5d09e06 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -15,19 +15,10 @@
 package com.android.systemui.tuner;
 
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.UserHandle;
-import android.provider.Settings;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 public abstract class TunerService {
 
@@ -47,6 +38,16 @@
     public abstract void addTunable(Tunable tunable, String... keys);
     public abstract void removeTunable(Tunable tunable);
 
+    /**
+     * Sets the state of the {@link TunerActivity} component for the current user
+     */
+    public abstract void setTunerEnabled(boolean enabled);
+
+    /**
+     * Returns true if the tuner is enabled for the current user.
+     */
+    public abstract boolean isTunerEnabled();
+
     public interface Tunable {
         void onTuningChanged(String key, String newValue);
     }
@@ -55,38 +56,6 @@
         mContext = context;
     }
 
-    private static Context userContext(Context context, UserHandle user) {
-        try {
-            return context.createPackageContextAsUser(context.getPackageName(), 0, user);
-        } catch (NameNotFoundException e) {
-            return context;
-        }
-    }
-
-    /** Enables or disables the tuner for the supplied user. */
-    public void setTunerEnabled(UserHandle user, boolean enabled) {
-        setTunerEnabled(mContext, user, enabled);
-    }
-
-    public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
-        userContext(context, user).getPackageManager().setComponentEnabledSetting(
-                new ComponentName(context, TunerActivity.class),
-                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
-    }
-
-    /** Returns true if the tuner is enabled for the supplied user. */
-    public boolean isTunerEnabled(UserHandle user) {
-        return isTunerEnabled(mContext, user);
-    }
-
-    public static final boolean isTunerEnabled(Context context, UserHandle user) {
-        return userContext(context, user).getPackageManager().getComponentEnabledSetting(
-                new ComponentName(context, TunerActivity.class))
-                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-    }
-
     public static class ClearReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -97,35 +66,7 @@
     }
 
     /** */
-    public void showResetRequest(UserHandle user, final Runnable onDisabled) {
-        showResetRequest(mContext, user, onDisabled);
-    }
-
-    public static final void showResetRequest(final Context context, UserHandle user,
-            final Runnable onDisabled) {
-        SystemUIDialog dialog = new SystemUIDialog(context);
-        dialog.setShowForAllUsers(true);
-        dialog.setMessage(R.string.remove_from_settings_prompt);
-        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
-                (OnClickListener) null);
-        dialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        // Tell the tuner (in main SysUI process) to clear all its settings.
-                        context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
-                        // Disable access to tuner.
-                        TunerService.setTunerEnabled(context, user, false);
-                        // Make them sit through the warning dialog again.
-                        Settings.Secure.putInt(context.getContentResolver(),
-                                TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
-                        if (onDisabled != null) {
-                            onDisabled.run();
-                        }
-                    }
-                });
-        dialog.show();
-    }
+    public abstract void showResetRequest(Runnable onDisabled);
 
     public static boolean parseIntegerSwitch(String value, boolean defaultValue) {
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 027c282b..e9e4380 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,8 +15,12 @@
  */
 package com.android.systemui.tuner;
 
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -32,13 +36,14 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.leak.LeakDetector;
 
 import java.util.HashSet;
@@ -83,6 +88,7 @@
     private int mCurrentUser;
     private UserTracker.Callback mCurrentUserTracker;
     private UserTracker mUserTracker;
+    private final ComponentName mTunerComponent;
 
     /**
      */
@@ -92,7 +98,6 @@
             @Main Handler mainHandler,
             LeakDetector leakDetector,
             DemoModeController demoModeController,
-            BroadcastDispatcher broadcastDispatcher,
             UserTracker userTracker) {
         super(context);
         mContext = context;
@@ -100,6 +105,7 @@
         mLeakDetector = leakDetector;
         mDemoModeController = demoModeController;
         mUserTracker = userTracker;
+        mTunerComponent = new ComponentName(mContext, TunerActivity.class);
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
             mCurrentUser = user.getUserHandle().getIdentifier();
@@ -142,7 +148,7 @@
             }
         }
         if (oldVersion < 2) {
-            setTunerEnabled(mContext, mUserTracker.getUserHandle(), false);
+            setTunerEnabled(false);
         }
         // 3 Removed because of a revert.
         if (oldVersion < 4) {
@@ -269,6 +275,46 @@
         }
     }
 
+
+    @Override
+    public void setTunerEnabled(boolean enabled) {
+        mUserTracker.getUserContext().getPackageManager().setComponentEnabledSetting(
+                mTunerComponent,
+                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP
+        );
+    }
+
+    @Override
+    public boolean isTunerEnabled() {
+        return mUserTracker.getUserContext().getPackageManager().getComponentEnabledSetting(
+                mTunerComponent) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+    }
+
+    @Override
+    public void showResetRequest(Runnable onDisabled) {
+        SystemUIDialog dialog = new SystemUIDialog(mContext);
+        dialog.setShowForAllUsers(true);
+        dialog.setMessage(R.string.remove_from_settings_prompt);
+        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mContext.getString(R.string.cancel),
+                (DialogInterface.OnClickListener) null);
+        dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+                mContext.getString(R.string.guest_exit_guest_dialog_remove), (d, which) -> {
+                    // Tell the tuner (in main SysUI process) to clear all its settings.
+                    mContext.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+                    // Disable access to tuner.
+                    setTunerEnabled(false);
+                    // Make them sit through the warning dialog again.
+                    Secure.putInt(mContext.getContentResolver(),
+                            TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+                    if (onDisabled != null) {
+                        onDisabled.run();
+                    }
+                });
+        dialog.show();
+    }
+
     private class Observer extends ContentObserver {
         public Observer() {
             super(new Handler(Looper.getMainLooper()));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 854be1f..14b4d02 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -26,11 +26,16 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowInsetsController;
+import android.widget.FrameLayout;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -45,12 +50,21 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper()
 public class KeyguardSecurityContainerTest extends SysuiTestCase {
+    private static final int SCREEN_WIDTH = 1600;
+    private static final int FAKE_MEASURE_SPEC =
+            View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, View.MeasureSpec.EXACTLY);
+
+    private static final SecurityMode ONE_HANDED_SECURITY_MODE = SecurityMode.PIN;
+    private static final SecurityMode TWO_HANDED_SECURITY_MODE = SecurityMode.Password;
+
+
 
     @Rule
     public MockitoRule mRule = MockitoJUnit.rule();
 
     @Mock
     private WindowInsetsController mWindowInsetsController;
+
     @Mock
     private KeyguardSecurityViewFlipper mSecurityViewFlipper;
 
@@ -58,9 +72,18 @@
 
     @Before
     public void setup() {
+        // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
+        // the real references (rather than the TestableResources that this call creates).
+        mContext.ensureTestableResources();
+        FrameLayout.LayoutParams securityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
         when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(securityViewFlipperLayoutParams);
         mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
         mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
+        mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
     }
 
     @Test
@@ -69,4 +92,75 @@
         verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
                 any(), any());
     }
-}
\ No newline at end of file
+
+    @Test
+    public void onMeasure_usesFullWidthWithoutOneHandedMode() {
+        setUpKeyguard(
+                /* deviceConfigCanUseOneHandedKeyguard= */false,
+                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
+                ONE_HANDED_SECURITY_MODE);
+
+        mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+
+        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+    }
+
+    @Test
+    public void onMeasure_usesFullWidthWithDeviceFlagDisabled() {
+        setUpKeyguard(
+                /* deviceConfigCanUseOneHandedKeyguard= */false,
+                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
+                ONE_HANDED_SECURITY_MODE);
+
+        mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+    }
+
+    @Test
+    public void onMeasure_usesFullWidthWithSysUIFlagDisabled() {
+        setUpKeyguard(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */ false,
+                ONE_HANDED_SECURITY_MODE);
+
+        mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+    }
+
+    @Test
+    public void onMeasure_usesHalfWidthWithFlagsEnabled() {
+        setUpKeyguard(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
+                ONE_HANDED_SECURITY_MODE);
+
+        int halfWidthMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY);
+        mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+
+        verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, FAKE_MEASURE_SPEC);
+    }
+
+    @Test
+    public void onMeasure_usesFullWidthForFullScreenIme() {
+        setUpKeyguard(
+                /* deviceConfigCanUseOneHandedKeyguard= */true,
+                /* sysuiResourceCanUseOneHandedKeyguard= */ true,
+                TWO_HANDED_SECURITY_MODE);
+
+        mKeyguardSecurityContainer.onMeasure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+        verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
+    }
+
+    private void setUpKeyguard(
+            boolean deviceConfigCanUseOneHandedKeyguard,
+            boolean sysuiResourceCanUseOneHandedKeyguard,
+            SecurityMode securityMode) {
+        TestableResources testableResources = mContext.getOrCreateTestableResources();
+        testableResources.addOverride(com.android.internal.R.bool.config_enableOneHandedKeyguard,
+                deviceConfigCanUseOneHandedKeyguard);
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer,
+                sysuiResourceCanUseOneHandedKeyguard);
+        mKeyguardSecurityContainer.updateLayoutForSecurityMode(securityMode);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 6c3b37e..ba21afd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -309,17 +309,6 @@
     }
 
     @Test
-    public void testOverlayPredicate() {
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_overlay = makeMockSBN(USERID_ONE, "android",
-                0, "AlertWindowNotification", Notification.FLAG_NO_CLEAR);
-
-        assertTrue(mFsc.isSystemAlertNotification(sbn_user1_overlay));
-        assertFalse(mFsc.isSystemAlertNotification(sbn_user1_app1));
-    }
-
-    @Test
     public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
         // no notifications nor app op signals that this package/userId requires system alert
         // warning
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 0c69ffd..e1ddaad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -43,6 +43,7 @@
 import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -53,6 +54,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
+@Ignore
 public class ImageWallpaperTest extends SysuiTestCase {
     private static final int LOW_BMP_WIDTH = 128;
     private static final int LOW_BMP_HEIGHT = 128;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index bc322f7..97cb873 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -69,6 +69,7 @@
 @TestableLooper.RunWithLooper
 public class AppOpsControllerTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
+    private static final String TEST_ATTRIBUTION_NAME = "attribution";
     private static final int TEST_UID = UserHandle.getUid(0, 0);
     private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
     private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@@ -164,7 +165,7 @@
         mController.onOpActiveChanged(
                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         mTestableLooper.processAllMessages();
         verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID, TEST_PACKAGE_NAME, true);
@@ -218,8 +219,8 @@
         mController.onOpActiveChanged(AppOpsManager.OP_CAMERA,
                 TEST_UID, TEST_PACKAGE_NAME, true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION,
-                TEST_UID, TEST_PACKAGE_NAME, AppOpsManager.OP_FLAG_SELF,
-                AppOpsManager.MODE_ALLOWED);
+                TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
+                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         assertEquals(3, mController.getActiveAppOps().size());
     }
 
@@ -230,8 +231,8 @@
         mController.onOpActiveChanged(AppOpsManager.OP_CAMERA,
                 TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION,
-                TEST_UID, TEST_PACKAGE_NAME, AppOpsManager.OP_FLAG_SELF,
-                AppOpsManager.MODE_ALLOWED);
+                TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
+                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         assertEquals(2,
                 mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
         assertEquals(1,
@@ -262,7 +263,7 @@
     public void opNotedScheduledForRemoval() {
         mController.setBGHandler(mMockHandler);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         verify(mMockHandler).scheduleRemoval(any(AppOpItem.class), anyLong());
     }
 
@@ -274,7 +275,7 @@
         mController.onOpActiveChanged(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
                 true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         assertFalse(mController.getActiveAppOps().isEmpty());
 
         mController.setListening(false);
@@ -288,9 +289,9 @@
         mController.setBGHandler(mMockHandler);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         // Only one post to notify subscribers
         verify(mMockHandler, times(1)).post(any());
@@ -304,9 +305,9 @@
         mController.setBGHandler(mMockHandler);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         // Only one post to notify subscribers
         verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
@@ -324,7 +325,7 @@
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         // Check that we "scheduled" the removal. Don't actually schedule until we are ready to
         // process messages at a later time.
@@ -353,7 +354,7 @@
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         mController.onOpActiveChanged(
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
@@ -382,7 +383,7 @@
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         mController.onOpActiveChanged(
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
@@ -400,7 +401,7 @@
                 AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
-                AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
+                TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
 
         mTestableLooper.processAllMessages();
         verify(mCallback).onActiveStateChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index d607727..afe5c0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -41,6 +41,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.WakeLockFake;
@@ -74,6 +75,8 @@
     private Handler mHandler;
     private HandlerThread mHandlerThread;
     private DozeUi mDozeUi;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() throws Exception {
@@ -85,7 +88,8 @@
         mHandler = mHandlerThread.getThreadHandler();
 
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
-                mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService);
+                mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
+                () -> mStatusBarStateController);
         mDozeUi.setDozeMachine(mMachine);
     }
 
@@ -141,7 +145,8 @@
         reset(mHost);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
-                mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService);
+                mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
+                () -> mStatusBarStateController);
         mDozeUi.setDozeMachine(mMachine);
 
         // Never animate if display doesn't support it.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
index e23507b..a3221b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -50,6 +50,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@Ignore
 public class EglHelperTest extends SysuiTestCase {
 
     @Spy
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java
index 24f3eb2..510b907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java
@@ -33,6 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,6 +44,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@Ignore
 public class ImageWallpaperRendererTest extends SysuiTestCase {
 
     private WallpaperManager mWpmSpy;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index d3dbe2b..ccd9548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
@@ -37,9 +36,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.FeatureFlags
-import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.settings.FakeSettings
@@ -49,7 +46,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Answers
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
@@ -57,6 +53,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -73,6 +70,7 @@
     private lateinit var activityStarter: ActivityStarter
     @Mock
     private lateinit var qsLogger: QSLogger
+    @Mock
     private lateinit var controlsComponent: ControlsComponent
     @Mock
     private lateinit var controlsUiController: ControlsUiController
@@ -96,54 +94,64 @@
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DeviceControlsTile
 
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock
     private lateinit var secureSettings: SecureSettings
+    private var featureEnabled = true
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        secureSettings = FakeSettings()
 
         `when`(qsHost.context).thenReturn(mContext)
         `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+        `when`(controlsController.available).thenReturn(true)
+        `when`(controlsComponent.isEnabled()).thenReturn(true)
+        secureSettings.putInt(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 1)
 
-        controlsComponent = ControlsComponent(
-                true,
-                mContext,
-                { controlsController },
-                { controlsUiController },
-                { controlsListingController },
-                lockPatternUtils,
-                keyguardStateController,
-                userTracker,
-                secureSettings
-        )
+        setupControlsComponent()
 
         globalSettings = FakeSettings()
 
         globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
         `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
 
-        `when`(userTracker.userHandle.identifier).thenReturn(0)
-
         tile = createTile()
     }
 
+    private fun setupControlsComponent() {
+        `when`(controlsComponent.getControlsController()).thenAnswer {
+            if (featureEnabled) {
+                Optional.of(controlsController)
+            } else {
+                Optional.empty()
+            }
+        }
+
+        `when`(controlsComponent.getControlsListingController()).thenAnswer {
+            if (featureEnabled) {
+                Optional.of(controlsListingController)
+            } else {
+                Optional.empty()
+            }
+        }
+
+        `when`(controlsComponent.getControlsUiController()).thenAnswer {
+            if (featureEnabled) {
+                Optional.of(controlsUiController)
+            } else {
+                Optional.empty()
+            }
+        }
+    }
+
     @Test
     fun testAvailable() {
-        `when`(controlsController.available).thenReturn(true)
         assertThat(tile.isAvailable).isTrue()
     }
 
     @Test
     fun testNotAvailableFeature() {
-        `when`(controlsController.available).thenReturn(true)
         `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
 
         assertThat(tile.isAvailable).isFalse()
@@ -151,24 +159,22 @@
 
     @Test
     fun testNotAvailableControls() {
-        controlsComponent = ControlsComponent(
-                false,
-                mContext,
-                { controlsController },
-                { controlsUiController },
-                { controlsListingController },
-                lockPatternUtils,
-                keyguardStateController,
-                userTracker,
-                secureSettings
-        )
+        featureEnabled = false
         tile = createTile()
 
         assertThat(tile.isAvailable).isFalse()
     }
 
     @Test
-    fun testNotAvailableFlag() {
+    fun testAvailableControlsSettingOff() {
+        `when`(controlsController.available).thenReturn(false)
+
+        tile = createTile()
+        assertThat(tile.isAvailable).isTrue()
+    }
+
+    @Test
+    fun testNotAvailableControlsLockscreenFlag() {
         globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
         tile = createTile()
 
@@ -207,11 +213,26 @@
     }
 
     @Test
+    fun testStateUnavailableIfNotEnabled() {
+        verify(controlsListingController).observe(
+            any(LifecycleOwner::class.java),
+            capture(listingCallbackCaptor)
+        )
+        `when`(controlsComponent.isEnabled()).thenReturn(false)
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
     fun testStateAvailableIfListings() {
         verify(controlsListingController).observe(
                 any(LifecycleOwner::class.java),
                 capture(listingCallbackCaptor)
         )
+        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -220,6 +241,21 @@
     }
 
     @Test
+    fun testStateInactiveIfLocked() {
+        verify(controlsListingController).observe(
+            any(LifecycleOwner::class.java),
+            capture(listingCallbackCaptor)
+        )
+        `when`(controlsComponent.getVisibility())
+            .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
+    }
+
+    @Test
     fun testMoveBetweenStates() {
         verify(controlsListingController).observe(
                 any(LifecycleOwner::class.java),
@@ -249,6 +285,7 @@
                 any(LifecycleOwner::class.java),
                 capture(listingCallbackCaptor)
         )
+        `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
 
         listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
         testableLooper.processAllMessages()
@@ -260,6 +297,24 @@
     }
 
     @Test
+    fun testNoDialogWhenInactive() {
+        verify(controlsListingController).observe(
+            any(LifecycleOwner::class.java),
+            capture(listingCallbackCaptor)
+        )
+        `when`(controlsComponent.getVisibility())
+            .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+    }
+
+    @Test
     fun testDialogDismissedOnDestroy() {
         verify(controlsListingController).observe(
                 any(LifecycleOwner::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 05cf33a..b493b9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -171,62 +171,10 @@
     }
 
     @Test
-    public void testSuppressSystemAlertNotification() {
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
-        when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
-        StatusBarNotification sbn = mRow.getEntry().getSbn();
-        Bundle bundle = new Bundle();
-        bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"});
-        sbn.getNotification().extras = bundle;
-
-        assertTrue(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-    }
-
-    @Test
-    public void testDoNotSuppressSystemAlertNotification() {
-        StatusBarNotification sbn = mRow.getEntry().getSbn();
-        Bundle bundle = new Bundle();
-        bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"});
-        sbn.getNotification().extras = bundle;
-
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
-        when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
-
-        assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
-        when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
-
-        assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
-        when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
-
-        assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-    }
-
-    @Test
-    public void testDoNotSuppressMalformedSystemAlertNotification() {
-        when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
-
-        // missing extra
-        assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-
-        StatusBarNotification sbn = mRow.getEntry().getSbn();
-        Bundle bundle = new Bundle();
-        bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{});
-        sbn.getNotification().extras = bundle;
-
-        // extra missing values
-        assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
-    }
-
-    @Test
     public void testShouldFilterHiddenNotifications() {
         initStatusBarNotification(false);
         // setup
         when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
-        when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
 
         // test should filter out hidden notifications:
         // hidden
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
index 0954621..2784568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
@@ -115,41 +115,6 @@
     }
 
     @Test
-    public void filterTest_systemAlertNotificationUnnecessary() {
-        // GIVEN the alert notification isn't needed for this user
-        final Bundle extras = new Bundle();
-        extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS,
-                new String[]{TEST_PKG});
-        mEntryBuilder.modifyNotification(mContext)
-                .setExtras(extras);
-        NotificationEntry entry = mEntryBuilder.build();
-        StatusBarNotification sbn = entry.getSbn();
-        when(mForegroundServiceController.isSystemAlertWarningNeeded(sbn.getUserId(), TEST_PKG))
-                .thenReturn(false);
-
-        // GIVEN the notification is a system alert notification + not a disclosure notification
-        when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(true);
-        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
-
-
-        // THEN filter out the notification
-        assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
-    }
-
-    @Test
-    public void filterTest_doNotFilter() {
-        NotificationEntry entry = mEntryBuilder.build();
-        StatusBarNotification sbn = entry.getSbn();
-
-        // GIVEN the notification isn't a system alert notification nor a disclosure notification
-        when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(false);
-        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
-
-        // THEN don't filter out the notification
-        assertFalse(mForegroundFilter.shouldFilterOut(entry, 0));
-    }
-
-    @Test
     public void testIncludeFGSInSection_importanceDefault() {
         // GIVEN the notification represents a colorized foreground service with > min importance
         mEntryBuilder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 461f64e..84fb368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -21,6 +21,8 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 
@@ -49,6 +51,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
@@ -101,6 +104,7 @@
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     @UiThreadTest
@@ -139,8 +143,8 @@
                 mGroupMembershipManger,
                 mGroupExpansionManager,
                 mStatusBarStateController,
-                mAmbientState
-        );
+                mAmbientState,
+                mFeatureFlags);
         mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
                 mNotificationSwipeHelper);
         mStackScroller = spy(mStackScrollerInternal);
@@ -205,8 +209,8 @@
     @Test
     @UiThreadTest
     public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
-        final float expectedHeight[] = {0f};
-        final float expectedAppear[] = {0f};
+        final float[] expectedHeight = {0f};
+        final float[] expectedAppear = {0f};
 
         mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> {
             Assert.assertEquals(expectedHeight[0], height, 0);
@@ -222,6 +226,29 @@
     }
 
     @Test
+    @UiThreadTest
+    public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        final int[] expectedStackHeight = {0};
+
+        mStackScroller.addOnExpandedHeightChangedListener((expandedHeight, appear) -> {
+            assertWithMessage("Given shade enabled: %s",
+                    mFeatureFlags.isTwoColumnNotificationShadeEnabled())
+                    .that(mStackScroller.getHeight())
+                    .isEqualTo(expectedStackHeight[0]);
+        });
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        expectedStackHeight[0] = 0;
+        mStackScroller.setExpandedHeight(100f);
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        expectedStackHeight[0] = 100;
+        mStackScroller.setExpandedHeight(100f);
+    }
+
+
+    @Test
     public void manageNotifications_visible() {
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 8dea84c..6e0cbd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -197,9 +198,9 @@
         mHeadsUpAppearanceController.destroy();
         verify(mHeadsUpManager).removeListener(any());
         verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
-        verify(mPanelView).removeVerticalTranslationListener(any());
+        verify(mPanelView).setVerticalTranslationListener(isNull());
         verify(mPanelView).removeTrackingHeadsUpListener(any());
-        verify(mPanelView).setHeadsUpAppearanceController(any());
+        verify(mPanelView).setHeadsUpAppearanceController(isNull());
         verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
         verify(mStackScrollerController).removeOnLayoutChangeListener(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index e788a1c..dd31f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -18,19 +18,22 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.IdRes;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.content.res.Configuration;
@@ -45,6 +48,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -53,6 +57,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardClockSwitch;
@@ -100,7 +105,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
@@ -116,8 +120,6 @@
     @Mock
     private StatusBar mStatusBar;
     @Mock
-    private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock
     private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -227,7 +229,10 @@
     private AmbientState mAmbientState;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private UiEventLogger mUiEventLogger;
 
+    private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
@@ -235,6 +240,8 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger);
+
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
         when(mView.getResources()).thenReturn(mResources);
@@ -258,12 +265,14 @@
         when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
         when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
         when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
+        when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
         when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
-        when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
         mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
+        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
         when(mView.findViewById(R.id.notification_container_parent))
                 .thenReturn(mNotificationContainerParent);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
@@ -293,10 +302,6 @@
                 .thenReturn(mKeyguardClockSwitchController);
         when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
                 .thenReturn(mKeyguardStatusViewController);
-        when(mQsFrame.getLayoutParams()).thenReturn(
-                new ViewGroup.LayoutParams(600, 400));
-        when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn(
-                new ViewGroup.LayoutParams(600, 400));
 
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
@@ -336,17 +341,19 @@
                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
         verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
         mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
+        mNotificationPanelViewController.mStatusBarStateController
+                .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
+        mNotificationPanelViewController
+                .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
     }
 
     @Test
     public void testSetDozing_notifiesNsslAndStateController() {
-        mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
+        mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
                 null /* touch */);
-        InOrder inOrder = inOrder(
-                mNotificationStackScrollLayoutController, mStatusBarStateController);
-        inOrder.verify(mNotificationStackScrollLayoutController)
-                .setDozing(eq(true), eq(true), eq(null));
-        inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
+        verify(mNotificationStackScrollLayoutController)
+                .setDozing(eq(true), eq(false), eq(null));
+        assertThat(mStatusBarStateController.getDozeAmount()).isEqualTo(1f);
     }
 
     @Test
@@ -442,9 +449,8 @@
 
     @Test
     public void testAllChildrenOfNotificationContainer_haveIds() {
-        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
-        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
-
+        enableSplitShade();
+        mNotificationContainerParent.removeAllViews();
         mNotificationContainerParent.addView(newViewWithId(1));
         mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
 
@@ -457,36 +463,92 @@
     @Test
     public void testSinglePaneShadeLayout_isAlignedToParent() {
         when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
-        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
-        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
 
         mNotificationPanelViewController.updateResources();
 
-        ConstraintSet constraintSet = new ConstraintSet();
-        constraintSet.clone(mNotificationContainerParent);
-        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
-        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
-                R.id.notification_stack_scroller).layout;
-        assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID);
-        assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID);
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+                .isEqualTo(ConstraintSet.PARENT_ID);
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+                .isEqualTo(ConstraintSet.PARENT_ID);
     }
 
     @Test
     public void testSplitShadeLayout_isAlignedToGuideline() {
-        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
-        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
-        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
-        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+        enableSplitShade();
 
         mNotificationPanelViewController.updateResources();
 
-        ConstraintSet constraintSet = new ConstraintSet();
-        constraintSet.clone(mNotificationContainerParent);
-        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
-        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
-                R.id.notification_stack_scroller).layout;
-        assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline);
-        assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline);
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+                .isEqualTo(R.id.qs_edge_guideline);
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+                .isEqualTo(R.id.qs_edge_guideline);
+    }
+
+    @Test
+    public void testSinglePaneShadeLayout_childrenHaveConstantWidth() {
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+
+        mNotificationPanelViewController.updateResources();
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).mWidth)
+                .isEqualTo(mResources.getDimensionPixelSize(R.dimen.qs_panel_width));
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).mWidth)
+                .isEqualTo(mResources.getDimensionPixelSize(R.dimen.notification_panel_width));
+    }
+
+    @Test
+    public void testSplitShadeLayout_childrenHaveZeroWidth() {
+        enableSplitShade();
+
+        mNotificationPanelViewController.updateResources();
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).mWidth).isEqualTo(0);
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).mWidth).isEqualTo(0);
+    }
+
+    @Test
+    public void testOnDragDownEvent_horizontalTranslationIsZeroForSplitShade() {
+        when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(350f);
+        when(mView.getWidth()).thenReturn(800);
+        enableSplitShade();
+
+        onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN,
+                200f /* x position */, 0f, 0));
+
+        verify(mQsFrame).setTranslationX(0);
+    }
+
+    @Test
+    public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
+        mStatusBarStateController.setState(KEYGUARD);
+
+        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
+    }
+
+    @Test
+    public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() {
+        mStatusBarStateController.setState(SHADE);
+        when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true);
+
+        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
+    }
+
+    @Test
+    public void testCanCollapsePanelOnTouch_trueWhenInSettings() {
+        mStatusBarStateController.setState(SHADE);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue();
+    }
+
+    @Test
+    public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
+        mStatusBarStateController.setState(SHADE);
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
     }
 
     private View newViewWithId(int id) {
@@ -499,6 +561,17 @@
         return view;
     }
 
+    private ConstraintSet.Layout getConstraintSetLayout(@IdRes int id) {
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        return constraintSet.getConstraint(id).layout;
+    }
+
+    private void enableSplitShade() {
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+    }
+
     private void onTouchEvent(MotionEvent ev) {
         mTouchHandler.onTouch(mView, ev);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
index 97d4aa7..7d8a288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.utils.leaks;
 
-import android.os.UserHandle;
 import android.testing.LeakCheck;
 
 import com.android.systemui.tuner.TunerService;
@@ -78,12 +77,15 @@
     }
 
     @Override
-    public void setTunerEnabled(UserHandle user, boolean enabled) {
+    public void setTunerEnabled(boolean enabled) {
         mEnabled = enabled;
     }
 
     @Override
-    public boolean isTunerEnabled(UserHandle user) {
+    public boolean isTunerEnabled() {
         return mEnabled;
     }
+
+    @Override
+    public void showResetRequest(Runnable onDisabled) {}
 }
diff --git a/services/core/java/android/power/PowerStatsInternal.java b/services/core/java/android/power/PowerStatsInternal.java
index 40107a5..27da142 100644
--- a/services/core/java/android/power/PowerStatsInternal.java
+++ b/services/core/java/android/power/PowerStatsInternal.java
@@ -50,7 +50,8 @@
      *                          requested.
      *
      * @return A Future containing a list of {@link EnergyConsumerResult} objects containing energy
-     *         consumer results for all listed {@link EnergyConsumerId}.
+     *         consumer results for all listed {@link EnergyConsumerId}. null if
+     *         {@link EnergyConsumer} not supported
      */
     @NonNull
     public abstract CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
@@ -59,8 +60,10 @@
     /**
      * Returns the power entity info for all available {@link PowerEntity}
      *
-     * @return List of available {@link PowerEntity}
+     * @return List of available {@link PowerEntity} or null if {@link PowerEntity} not
+     * supported
      */
+    @Nullable
     public abstract PowerEntity[] getPowerEntityInfo();
 
     /**
@@ -71,7 +74,8 @@
      *                          requested.
      *
      * @return A Future containing a list of {@link StateResidencyResult} objects containing state
-     *         residency results for all listed {@link PowerEntity.id}.
+     *         residency results for all listed {@link PowerEntity.id}. null if {@link PowerEntity}
+     *         not supported
      */
     @NonNull
     public abstract CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
@@ -80,8 +84,9 @@
     /**
      * Returns the channel info for all available {@link Channel}
      *
-     * @return List of available {@link Channel}
+     * @return List of available {@link Channel} or null if {@link Channel} not supported
      */
+    @Nullable
     public abstract Channel[] getEnergyMeterInfo();
 
     /**
@@ -91,7 +96,8 @@
      * @param channelIds Array of {@link Channel.id} for accumulated energy is being requested.
      *
      * @return A Future containing a list of {@link EnergyMeasurement} objects containing
-     *         accumulated energy measurements for all listed {@link Channel.id}.
+     *         accumulated energy measurements for all listed {@link Channel.id}. null if
+     *         {@link Channel} not supported
      */
     @NonNull
     public abstract CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index edaf6a90..84e429d 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
 import static android.app.ActivityManager.RunningServiceInfo;
 import static android.app.ActivityManager.RunningTaskInfo;
 import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -197,18 +198,20 @@
                                     Intent.EXTRA_USER)).getIdentifier(),
                             intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
                 }
-            }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY));
+            }, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
+                    MANAGE_SENSOR_PRIVACY, null);
         }
 
         @Override
-        public void onOpStarted(int code, int uid, String packageName,
+        public void onOpStarted(int code, int uid, String packageName, String attributionTag,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
-            onOpNoted(code, uid, packageName, flags, result);
+            onOpNoted(code, uid, packageName, attributionTag, flags, result);
         }
 
         @Override
         public void onOpNoted(int code, int uid, String packageName,
-                @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+                String attributionTag, @AppOpsManager.OpFlags int flags,
+                @AppOpsManager.Mode int result) {
             if (result != MODE_ALLOWED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
                 return;
             }
@@ -459,12 +462,12 @@
          */
         private void enforceSensorPrivacyPermission() {
             if (mContext.checkCallingOrSelfPermission(
-                    android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
+                    MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
                 return;
             }
             throw new SecurityException(
                     "Changing sensor privacy requires the following permission: "
-                            + android.Manifest.permission.MANAGE_SENSOR_PRIVACY);
+                            + MANAGE_SENSOR_PRIVACY);
         }
 
         /**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a4ff230..5550999 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2111,7 +2111,8 @@
         private final AppOpsManager.OnOpNotedListener mOpNotedCallback =
                 new AppOpsManager.OnOpNotedListener() {
                     @Override
-                    public void onOpNoted(int op, int uid, String pkgName, int flags, int result) {
+                    public void onOpNoted(int op, int uid, String pkgName,
+                            String attributionTag, int flags, int result) {
                         incrementOpCountIfNeeded(op, uid, result);
                     }
         };
@@ -2119,7 +2120,8 @@
         private final AppOpsManager.OnOpStartedListener mOpStartedCallback =
                 new AppOpsManager.OnOpStartedListener() {
                     @Override
-                    public void onOpStarted(int op, int uid, String pkgName, int flags,
+                    public void onOpStarted(int op, int uid, String pkgName,
+                            String attributionTag, int flags,
                             int result) {
                         incrementOpCountIfNeeded(op, uid, result);
                     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6500f6d..167c2b1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -216,6 +216,8 @@
             return;
         }
 
+        if (results == null) return;
+
         for (int i = 0; i < results.length; i++) {
             final StateResidencyResult result = results[i];
             RpmStats.PowerStateSubsystem subsystem =
@@ -257,7 +259,7 @@
             return EMPTY;
         }
 
-        if (results.length == 0) return EMPTY;
+        if (results == null || results.length == 0) return EMPTY;
 
         int charsLeft = MAX_LOW_POWER_STATS_SIZE;
         StringBuilder builder = new StringBuilder("SubsystemPowerState");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index d79fb8a..0a8016c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@
 import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationExitInfo;
@@ -229,6 +230,22 @@
     private final ArrayDeque<ProcessRecord> mTmpQueue;
     private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
 
+    /**
+     * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate
+     * could be called recursively because of the indirect calls during the update;
+     * however the oomAdjUpdate itself doesn't support recursion - in this case we'd
+     * have to queue up the new targets found during the update, and perform another
+     * round of oomAdjUpdate at the end of last update.
+     */
+    @GuardedBy("mService")
+    private boolean mOomAdjUpdateOngoing = false;
+
+    /**
+     * Flag to mark if there is a pending full oomAdjUpdate.
+     */
+    @GuardedBy("mService")
+    private boolean mPendingFullOomAdjUpdate = false;
+
     private final PlatformCompatCache mPlatformCompatCache;
 
     private static class PlatformCompatCache {
@@ -439,6 +456,23 @@
         if (oomAdjAll && mConstants.OOMADJ_UPDATE_QUICK) {
             return updateOomAdjLSP(app, oomAdjReason);
         }
+        if (checkAndEnqueueOomAdjTargetLocked(app)) {
+            // Simply return true as there is an oomAdjUpdate ongoing
+            return true;
+        }
+        try {
+            mOomAdjUpdateOngoing = true;
+            return performUpdateOomAdjLSP(app, oomAdjAll, oomAdjReason);
+        } finally {
+            // Kick off the handling of any pending targets enqueued during the above update
+            mOomAdjUpdateOngoing = false;
+            updateOomAdjPendingTargetsLocked(oomAdjReason);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean performUpdateOomAdjLSP(ProcessRecord app, boolean oomAdjAll,
+            String oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
         final ProcessStateRecord state = app.mState;
         final boolean wasCached = state.isCached();
@@ -453,20 +487,21 @@
                 ? state.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
         // Check if this process is in the pending list too, remove from pending list if so.
         mPendingProcessSet.remove(app);
-        boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
+        boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp, false,
                 SystemClock.uptimeMillis());
         if (oomAdjAll
                 && (wasCached != state.isCached()
                     || state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
             // Changed to/from cached state, so apps after it in the LRU
             // list may also be changed.
-            updateOomAdjLSP(oomAdjReason);
+            performUpdateOomAdjLSP(oomAdjReason);
         }
+
         return success;
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private boolean updateOomAdjLSP(ProcessRecord app, int cachedAdj,
+    private boolean performUpdateOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord TOP_APP, boolean doingAll, long now) {
         if (app.getThread() == null) {
             return false;
@@ -519,6 +554,22 @@
 
     @GuardedBy({"mService", "mProcLock"})
     private void updateOomAdjLSP(String oomAdjReason) {
+        if (checkAndEnqueueOomAdjTargetLocked(null)) {
+            // Simply return as there is an oomAdjUpdate ongoing
+            return;
+        }
+        try {
+            mOomAdjUpdateOngoing = true;
+            performUpdateOomAdjLSP(oomAdjReason);
+        } finally {
+            // Kick off the handling of any pending targets enqueued during the above update
+            mOomAdjUpdateOngoing = false;
+            updateOomAdjPendingTargetsLocked(oomAdjReason);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void performUpdateOomAdjLSP(String oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
         // Clear any pending ones because we are doing a full update now.
         mPendingProcessSet.clear();
@@ -548,6 +599,23 @@
             return true;
         }
 
+        if (checkAndEnqueueOomAdjTargetLocked(app)) {
+            // Simply return true as there is an oomAdjUpdate ongoing
+            return true;
+        }
+
+        try {
+            mOomAdjUpdateOngoing = true;
+            return performUpdateOomAdjLSP(app, oomAdjReason);
+        } finally {
+            // Kick off the handling of any pending targets enqueued during the above update
+            mOomAdjUpdateOngoing = false;
+            updateOomAdjPendingTargetsLocked(oomAdjReason);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean performUpdateOomAdjLSP(ProcessRecord app, String oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReason);
@@ -567,7 +635,7 @@
         state.resetCachedInfo();
         // Check if this process is in the pending list too, remove from pending list if so.
         mPendingProcessSet.remove(app);
-        boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
+        boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp, false,
                 SystemClock.uptimeMillis());
         if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
                 && wasBackground == ActivityManager.isProcStateBackground(
@@ -696,14 +764,59 @@
     }
 
     /**
+     * Check if there is an ongoing oomAdjUpdate, enqueue the given process record
+     * to {@link #mPendingProcessSet} if there is one.
+     *
+     * @param app The target app to get an oomAdjUpdate, or a full oomAdjUpdate if it's null.
+     * @return {@code true} if there is an ongoing oomAdjUpdate.
+     */
+    @GuardedBy("mService")
+    private boolean checkAndEnqueueOomAdjTargetLocked(@Nullable ProcessRecord app) {
+        if (!mOomAdjUpdateOngoing) {
+            return false;
+        }
+        if (app != null) {
+            mPendingProcessSet.add(app);
+        } else {
+            mPendingFullOomAdjUpdate = true;
+        }
+        return true;
+    }
+
+    /**
      * Kick off an oom adj update pass for the pending targets which are enqueued via
      * {@link #enqueueOomAdjTargetLocked}.
      */
     @GuardedBy("mService")
     void updateOomAdjPendingTargetsLocked(String oomAdjReason) {
+        // First check if there is pending full update
+        if (mPendingFullOomAdjUpdate) {
+            mPendingFullOomAdjUpdate = false;
+            mPendingProcessSet.clear();
+            updateOomAdjLocked(oomAdjReason);
+            return;
+        }
         if (mPendingProcessSet.isEmpty()) {
             return;
         }
+
+        if (mOomAdjUpdateOngoing) {
+            // There's another oomAdjUpdate ongoing, return from here now;
+            // that ongoing update would call us again at the end of it.
+            return;
+        }
+        try {
+            mOomAdjUpdateOngoing = true;
+            performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
+        } finally {
+            // Kick off the handling of any pending targets enqueued during the above update
+            mOomAdjUpdateOngoing = false;
+            updateOomAdjPendingTargetsLocked(oomAdjReason);
+        }
+    }
+
+    @GuardedBy("mService")
+    private void performUpdateOomAdjPendingTargetsLocked(String oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReason);
@@ -1846,8 +1959,8 @@
                         computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
                                 cycleReEval, true);
                     } else {
-                        cstate.setCurRawAdj(cstate.getSetAdj());
-                        cstate.setCurRawProcState(cstate.getSetProcState());
+                        cstate.setCurRawAdj(cstate.getCurAdj());
+                        cstate.setCurRawProcState(cstate.getCurProcState());
                     }
 
                     int clientAdj = cstate.getCurRawAdj();
@@ -2130,8 +2243,8 @@
                 if (computeClients) {
                     computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
                 } else {
-                    cstate.setCurRawAdj(cstate.getSetAdj());
-                    cstate.setCurRawProcState(cstate.getSetProcState());
+                    cstate.setCurRawAdj(cstate.getCurAdj());
+                    cstate.setCurRawProcState(cstate.getCurProcState());
                 }
 
                 if (shouldSkipDueToCycle(state, cstate, procState, adj, cycleReEval)) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index c92abd4..a776458 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3124,7 +3124,7 @@
             final Ops ops = getOpsLocked(uid, packageName, attributionTag, bypass,
                     true /* edit */);
             if (ops == null) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                         AppOpsManager.MODE_IGNORED);
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName);
@@ -3142,7 +3142,7 @@
             final UidState uidState = ops.uidState;
             if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
                 attributedOp.rejected(uidState.state, flags);
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                         AppOpsManager.MODE_IGNORED);
                 return AppOpsManager.MODE_IGNORED;
             }
@@ -3155,7 +3155,8 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     attributedOp.rejected(uidState.state, flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, flags, uidMode);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            uidMode);
                     return uidMode;
                 }
             } else {
@@ -3167,7 +3168,8 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     attributedOp.rejected(uidState.state, flags);
-                    scheduleOpNotedIfNeededLocked(code, uid, packageName, flags, mode);
+                    scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+                            mode);
                     return mode;
                 }
             }
@@ -3177,7 +3179,7 @@
                                 + packageName + (attributionTag == null ? ""
                                 : "." + attributionTag));
             }
-            scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+            scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                     AppOpsManager.MODE_ALLOWED);
             attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, uidState.state,
                     flags);
@@ -3580,7 +3582,7 @@
             final Ops ops = getOpsLocked(uid, packageName, attributionTag, bypass, true /* edit */);
             if (ops == null) {
                 if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName,
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
                             flags, AppOpsManager.MODE_IGNORED);
                 }
                 if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
@@ -3590,7 +3592,7 @@
             final Op op = getOpLocked(ops, code, uid, true);
             if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
                 if (!dryRun) {
-                    scheduleOpStartedIfNeededLocked(code, uid, packageName,
+                    scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
                             flags, AppOpsManager.MODE_IGNORED);
                 }
                 return AppOpsManager.MODE_IGNORED;
@@ -3611,7 +3613,8 @@
                     }
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, flags, uidMode);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, uidMode);
                     }
                     return uidMode;
                 }
@@ -3626,7 +3629,8 @@
                             + packageName);
                     if (!dryRun) {
                         attributedOp.rejected(uidState.state, flags);
-                        scheduleOpStartedIfNeededLocked(code, uid, packageName, flags, mode);
+                        scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+                                flags, mode);
                     }
                     return mode;
                 }
@@ -3634,7 +3638,7 @@
             if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
                     + " package " + packageName);
             if (!dryRun) {
-                scheduleOpStartedIfNeededLocked(code, uid, packageName, flags,
+                scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                         AppOpsManager.MODE_ALLOWED);
                 try {
                     attributedOp.started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
@@ -3774,7 +3778,7 @@
     }
 
     private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
-            @OpFlags int flags, @Mode int result) {
+            String attributionTag, @OpFlags int flags, @Mode int result) {
         ArraySet<StartedCallback> dispatchedCallbacks = null;
         final int callbackListCount = mStartedWatchers.size();
         for (int i = 0; i < callbackListCount; i++) {
@@ -3799,18 +3803,21 @@
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyOpStarted,
-                this, dispatchedCallbacks, code, uid, pkgName, flags, result));
+                this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+                result));
     }
 
     private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
-            int code, int uid, String packageName, @OpFlags int flags, @Mode int result) {
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
         final long identity = Binder.clearCallingIdentity();
         try {
             final int callbackCount = callbacks.size();
             for (int i = 0; i < callbackCount; i++) {
                 final StartedCallback callback = callbacks.valueAt(i);
                 try {
-                    callback.mCallback.opStarted(code, uid, packageName, flags, result);
+                    callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+                            result);
                 } catch (RemoteException e) {
                     /* do nothing */
                 }
@@ -3821,7 +3828,7 @@
     }
 
     private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
-            @OpFlags int flags, @Mode int result) {
+            String attributionTag, @OpFlags int flags, @Mode int result) {
         ArraySet<NotedCallback> dispatchedCallbacks = null;
         final int callbackListCount = mNotedWatchers.size();
         for (int i = 0; i < callbackListCount; i++) {
@@ -3842,11 +3849,13 @@
         }
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyOpChecked,
-                this, dispatchedCallbacks, code, uid, packageName, flags, result));
+                this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+                result));
     }
 
     private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
-            int code, int uid, String packageName, @OpFlags int flags, @Mode int result) {
+            int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+            @Mode int result) {
         // There are features watching for checks in our process. The callbacks in
         // these features may require permissions our remote caller does not have.
         final long identity = Binder.clearCallingIdentity();
@@ -3855,7 +3864,8 @@
             for (int i = 0; i < callbackCount; i++) {
                 final NotedCallback callback = callbacks.valueAt(i);
                 try {
-                    callback.mCallback.opNoted(code, uid, packageName, flags, result);
+                    callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+                            result);
                 } catch (RemoteException e) {
                     /* do nothing */
                 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 29ee8b6..5f34053 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -726,7 +726,7 @@
     // caches the value returned by AudioSystem.isMicrophoneMuted()
     private boolean mMicMuteFromSystemCached;
 
-    private boolean mFastScrollSoundEffectsEnabled;
+    private boolean mNavigationRepeatSoundEffectsEnabled;
     private boolean mHomeSoundEffectEnabled;
 
     @GuardedBy("mSettingsLock")
@@ -2325,15 +2325,15 @@
                 VOL_ADJUST_NORMAL);
     }
 
-    public void setFastScrollSoundEffectsEnabled(boolean enabled) {
-        mFastScrollSoundEffectsEnabled = enabled;
+    public void setNavigationRepeatSoundEffectsEnabled(boolean enabled) {
+        mNavigationRepeatSoundEffectsEnabled = enabled;
     }
 
     /**
      * @return true if the fast scroll sound effects are enabled
      */
-    public boolean areFastScrollSoundEffectsEnabled() {
-        return mFastScrollSoundEffectsEnabled;
+    public boolean areNavigationRepeatSoundEffectsEnabled() {
+        return mNavigationRepeatSoundEffectsEnabled;
     }
 
     public void setHomeSoundEffectEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index c14bb3e..7031e02 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -52,6 +52,7 @@
  * used by AudioService. As its methods are called on the message handler thread
  * of AudioService, the actual work is offloaded to a dedicated thread.
  * This helps keeping AudioService responsive.
+ *
  * @hide
  */
 class SoundEffectsHelper {
@@ -89,15 +90,18 @@
         final String mFileName;
         int mSampleId;
         boolean mLoaded;  // for effects in SoundPool
+
         Resource(String fileName) {
             mFileName = fileName;
             mSampleId = EFFECT_NOT_IN_SOUND_POOL;
         }
+
         void unload() {
             mSampleId = EFFECT_NOT_IN_SOUND_POOL;
             mLoaded = false;
         }
     }
+
     // All the fields below are accessed by the worker thread exclusively
     private final List<Resource> mResources = new ArrayList<Resource>();
     private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
@@ -116,9 +120,9 @@
     }
 
     /**
-     *  Unloads samples from the sound pool.
-     *  This method can be called to free some memory when
-     *  sound effects are disabled.
+     * Unloads samples from the sound pool.
+     * This method can be called to free some memory when
+     * sound effects are disabled.
      */
     /*package*/ void unloadSoundEffects() {
         sendMsg(MSG_UNLOAD_EFFECTS, 0, 0, null, 0);
@@ -385,12 +389,12 @@
                     }
                 }
 
-                boolean fastScrollSoundEffectsParsed = allFastScrollSoundsParsed(parserCounter);
+                boolean navigationRepeatFxParsed = allNavigationRepeatSoundsParsed(parserCounter);
                 boolean homeSoundParsed = parserCounter.getOrDefault(AudioManager.FX_HOME, 0) > 0;
-                if (fastScrollSoundEffectsParsed || homeSoundParsed) {
+                if (navigationRepeatFxParsed || homeSoundParsed) {
                     AudioManager audioManager = mContext.getSystemService(AudioManager.class);
-                    if (audioManager != null && fastScrollSoundEffectsParsed) {
-                        audioManager.setFastScrollSoundEffectsEnabled(true);
+                    if (audioManager != null && navigationRepeatFxParsed) {
+                        audioManager.setNavigationRepeatSoundEffectsEnabled(true);
                     }
                     if (audioManager != null && homeSoundParsed) {
                         audioManager.setHomeSoundEffectEnabled(true);
@@ -410,13 +414,13 @@
         }
     }
 
-    private boolean allFastScrollSoundsParsed(Map<Integer, Integer> parserCounter) {
+    private boolean allNavigationRepeatSoundsParsed(Map<Integer, Integer> parserCounter) {
         int numFastScrollSoundEffectsParsed =
-                parserCounter.getOrDefault(AudioManager.FX_FAST_SCROLL_1, 0)
-                        + parserCounter.getOrDefault(AudioManager.FX_FAST_SCROLL_2, 0)
-                        + parserCounter.getOrDefault(AudioManager.FX_FAST_SCROLL_3, 0)
-                        + parserCounter.getOrDefault(AudioManager.FX_FAST_SCROLL_4, 0);
-        return numFastScrollSoundEffectsParsed == AudioManager.NUM_FAST_SCROLL_SOUND_EFFECTS;
+                parserCounter.getOrDefault(AudioManager.FX_FOCUS_NAVIGATION_REPEAT_1, 0)
+                        + parserCounter.getOrDefault(AudioManager.FX_FOCUS_NAVIGATION_REPEAT_2, 0)
+                        + parserCounter.getOrDefault(AudioManager.FX_FOCUS_NAVIGATION_REPEAT_3, 0)
+                        + parserCounter.getOrDefault(AudioManager.FX_FOCUS_NAVIGATION_REPEAT_4, 0);
+        return numFastScrollSoundEffectsParsed == AudioManager.NUM_NAVIGATION_REPEAT_SOUND_EFFECTS;
     }
 
     private int findOrAddResourceByFileName(String fileName) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 5e1df27..198ea3d 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -165,7 +165,7 @@
                     "Failed to take screenshot because internal display is disconnected");
             return false;
         }
-        boolean isWideColor = SurfaceControl.getActiveColorMode(token)
+        boolean isWideColor = SurfaceControl.getDynamicDisplayInfo(token).activeColorMode
                 == Display.COLOR_MODE_DISPLAY_P3;
 
         // Set mPrepared here so if initialization fails, resources can be cleaned up.
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 501533d..40cee66 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -466,7 +466,7 @@
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
         sb.append(", colorMode ").append(colorMode);
         sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
-        sb.append(", HdrCapabilities ").append(hdrCapabilities);
+        sb.append(", hdrCapabilities ").append(hdrCapabilities);
         sb.append(", allmSupported ").append(allmSupported);
         sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
         sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3b66236..7ce4f06 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -100,50 +100,48 @@
     private void tryConnectDisplayLocked(long physicalDisplayId) {
         final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
         if (displayToken != null) {
-            SurfaceControl.DisplayInfo info = SurfaceControl.getDisplayInfo(displayToken);
-            if (info == null) {
-                Slog.w(TAG, "No valid info found for display device " + physicalDisplayId);
+            SurfaceControl.StaticDisplayInfo staticInfo =
+                    SurfaceControl.getStaticDisplayInfo(displayToken);
+            if (staticInfo == null) {
+                Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);
                 return;
             }
-            SurfaceControl.DisplayMode[] displayModes =
-                    SurfaceControl.getDisplayModes(displayToken);
-            if (displayModes == null) {
+            SurfaceControl.DynamicDisplayInfo dynamicInfo =
+                    SurfaceControl.getDynamicDisplayInfo(displayToken);
+            if (dynamicInfo == null) {
+                Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);
+                return;
+            }
+            if (dynamicInfo.supportedDisplayModes == null) {
                 // There are no valid modes for this device, so we can't use it
                 Slog.w(TAG, "No valid modes found for display device " + physicalDisplayId);
                 return;
             }
-            int activeDisplayMode = SurfaceControl.getActiveDisplayMode(displayToken);
-            if (activeDisplayMode < 0) {
+            if (dynamicInfo.activeDisplayModeId < 0) {
                 // There is no active mode, and for now we don't have the
                 // policy to set one.
-                Slog.w(TAG, "No active mode found for display device " + physicalDisplayId);
+                Slog.w(TAG, "No valid active mode found for display device " + physicalDisplayId);
                 return;
             }
-            int activeColorMode = SurfaceControl.getActiveColorMode(displayToken);
-            if (activeColorMode < 0) {
+            if (dynamicInfo.activeColorMode < 0) {
                 // We failed to get the active color mode. We don't bail out here since on the next
                 // configuration pass we'll go ahead and set it to whatever it was set to last (or
                 // COLOR_MODE_NATIVE if this is the first configuration).
-                Slog.w(TAG, "Unable to get active color mode for display device " +
-                        physicalDisplayId);
-                activeColorMode = Display.COLOR_MODE_INVALID;
+                Slog.w(TAG, "No valid active color mode for display device " + physicalDisplayId);
+                dynamicInfo.activeColorMode = Display.COLOR_MODE_INVALID;
             }
-            SurfaceControl.DesiredDisplayModeSpecs modeSpecsSpecs =
+            SurfaceControl.DesiredDisplayModeSpecs modeSpecs =
                     SurfaceControl.getDesiredDisplayModeSpecs(displayToken);
-            int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
-            Display.HdrCapabilities hdrCapabilities =
-                    SurfaceControl.getHdrCapabilities(displayToken);
             LocalDisplayDevice device = mDevices.get(physicalDisplayId);
             if (device == null) {
                 // Display was added.
                 final boolean isDefaultDisplay = mDevices.size() == 0;
-                device = new LocalDisplayDevice(displayToken, physicalDisplayId, info, displayModes,
-                        activeDisplayMode, modeSpecsSpecs, colorModes, activeColorMode,
-                        hdrCapabilities, isDefaultDisplay);
+                device = new LocalDisplayDevice(displayToken, physicalDisplayId, staticInfo,
+                        dynamicInfo, modeSpecs, isDefaultDisplay);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
-            } else if (device.updateDisplayPropertiesLocked(info, displayModes, activeDisplayMode,
-                    modeSpecsSpecs, colorModes, activeColorMode, hdrCapabilities)) {
+            } else if (device.updateDisplayPropertiesLocked(staticInfo, dynamicInfo,
+                    modeSpecs)) {
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
         } else {
@@ -195,7 +193,6 @@
         private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
                 new DisplayModeDirector.DesiredDisplayModeSpecs();
         private boolean mDisplayModeSpecsInvalid;
-        private int mActiveDisplayModeId;
         private int mActiveColorMode;
         private Display.HdrCapabilities mHdrCapabilities;
         private boolean mAllmSupported;
@@ -204,8 +201,11 @@
         private boolean mGameContentTypeRequested;
         private boolean mSidekickActive;
         private SidekickInternal mSidekickInternal;
-        private SurfaceControl.DisplayInfo mDisplayInfo;
-        private SurfaceControl.DisplayMode[] mDisplayModes;
+        private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;
+        // The supported display modes according in SurfaceFlinger
+        private SurfaceControl.DisplayMode[] mSfDisplayModes;
+        // The active display mode in SurfaceFlinger
+        private SurfaceControl.DisplayMode mActiveSfDisplayMode;
         private Spline mSystemBrightnessToNits;
         private Spline mNitsToHalBrightness;
         private DisplayDeviceConfig mDisplayDeviceConfig;
@@ -214,15 +214,13 @@
                 new DisplayEventReceiver.FrameRateOverride[0];
 
         LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
-                SurfaceControl.DisplayInfo info, SurfaceControl.DisplayMode[] displayModes,
-                int activeDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs,
-                int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities,
-                boolean isDefaultDisplay) {
+                SurfaceControl.StaticDisplayInfo staticDisplayInfo,
+                SurfaceControl.DynamicDisplayInfo dynamicInfo,
+                SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isDefaultDisplay) {
             super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
             mPhysicalDisplayId = physicalDisplayId;
             mIsDefaultDisplay = isDefaultDisplay;
-            updateDisplayPropertiesLocked(info, displayModes, activeDisplayModeId, modeSpecs,
-                    colorModes, activeColorMode, hdrCapabilities);
+            updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay);
             mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
@@ -241,15 +239,15 @@
         /**
          * Returns true if there is a change.
          **/
-        public boolean updateDisplayPropertiesLocked(SurfaceControl.DisplayInfo info,
-                SurfaceControl.DisplayMode[] displayModes,
-                int activeDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs,
-                int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities) {
+        public boolean updateDisplayPropertiesLocked(SurfaceControl.StaticDisplayInfo staticInfo,
+                SurfaceControl.DynamicDisplayInfo dynamicInfo,
+                SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             boolean changed = updateDisplayModesLocked(
-                    displayModes, activeDisplayModeId, modeSpecs);
-            changed |= updateDisplayInfo(info);
-            changed |= updateColorModesLocked(colorModes, activeColorMode);
-            changed |= updateHdrCapabilitiesLocked(hdrCapabilities);
+                    dynamicInfo.supportedDisplayModes, dynamicInfo.activeDisplayModeId, modeSpecs);
+            changed |= updateStaticInfo(staticInfo);
+            changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
+                    dynamicInfo.activeColorMode);
+            changed |= updateHdrCapabilitiesLocked(dynamicInfo.hdrCapabilities);
 
             if (changed) {
                 mHavePendingChanges = true;
@@ -260,8 +258,9 @@
         public boolean updateDisplayModesLocked(
                 SurfaceControl.DisplayMode[] displayModes, int activeDisplayModeId,
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
-            mDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
-            mActiveDisplayModeId = activeDisplayModeId;
+            mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
+            mActiveSfDisplayMode = getModeById(displayModes, activeDisplayModeId);
+
             // Build an updated list of all existing modes.
             ArrayList<DisplayModeRecord> records = new ArrayList<>();
             boolean modesAdded = false;
@@ -282,7 +281,7 @@
 
                 // First, check to see if we've already added a matching mode. Since not all
                 // configuration options are exposed via Display.Mode, it's possible that we have
-                // multiple DisplayModess that would generate the same Display.Mode.
+                // multiple DisplayModes that would generate the same Display.Mode.
                 boolean existingMode = false;
                 for (DisplayModeRecord record : records) {
                     if (record.hasMatchingMode(mode)
@@ -312,9 +311,8 @@
 
             // Get the currently active mode
             DisplayModeRecord activeRecord = null;
-            for (int i = 0; i < records.size(); i++) {
-                DisplayModeRecord record = records.get(i);
-                if (record.hasMatchingMode(displayModes[activeDisplayModeId])) {
+            for (DisplayModeRecord record : records) {
+                if (record.hasMatchingMode(mActiveSfDisplayMode)) {
                     activeRecord = record;
                     break;
                 }
@@ -370,17 +368,17 @@
             // For a new display, we need to initialize the default mode ID.
             if (mDefaultModeId == NO_DISPLAY_MODE_ID) {
                 mDefaultModeId = activeRecord.mMode.getModeId();
-                mDefaultModeGroup = displayModes[activeDisplayModeId].group;
+                mDefaultModeGroup = mActiveSfDisplayMode.group;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
-                mDefaultModeGroup = displayModes[activeDisplayModeId].group;
+                mDefaultModeGroup = mActiveSfDisplayMode.group;
             } else if (findDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
                 Slog.w(TAG, "Default display mode no longer available, using currently"
                         + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
-                mDefaultModeGroup = displayModes[activeDisplayModeId].group;
+                mDefaultModeGroup = mActiveSfDisplayMode.group;
             }
 
             // Determine whether the display mode specs' base mode is still there.
@@ -454,11 +452,11 @@
             mSystemBrightnessToNits = sysToNits;
         }
 
-        private boolean updateDisplayInfo(SurfaceControl.DisplayInfo info) {
-            if (Objects.equals(mDisplayInfo, info)) {
+        private boolean updateStaticInfo(SurfaceControl.StaticDisplayInfo info) {
+            if (Objects.equals(mStaticDisplayInfo, info)) {
                 return false;
             }
-            mDisplayInfo = info;
+            mStaticDisplayInfo = info;
             return true;
         }
 
@@ -520,6 +518,17 @@
             return true;
         }
 
+        private SurfaceControl.DisplayMode getModeById(SurfaceControl.DisplayMode[] supportedModes,
+                int modeId) {
+            for (SurfaceControl.DisplayMode mode : supportedModes) {
+                if (mode.id == modeId) {
+                    return mode;
+                }
+            }
+            Slog.e(TAG, "Can't find display mode with id " + modeId);
+            return null;
+        }
+
         private DisplayModeRecord findDisplayModeRecord(SurfaceControl.DisplayMode mode,
                 List<Float> alternativeRefreshRates) {
             for (int i = 0; i < mSupportedModes.size(); i++) {
@@ -556,10 +565,9 @@
         @Override
         public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
             if (mInfo == null) {
-                SurfaceControl.DisplayMode mode = mDisplayModes[mActiveDisplayModeId];
                 mInfo = new DisplayDeviceInfo();
-                mInfo.width = mode.width;
-                mInfo.height = mode.height;
+                mInfo.width = mActiveSfDisplayMode.width;
+                mInfo.height = mActiveSfDisplayMode.height;
                 mInfo.modeId = mActiveModeId;
                 mInfo.defaultModeId = mDefaultModeId;
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
@@ -572,21 +580,21 @@
                     mInfo.supportedColorModes[i] = mSupportedColorModes.get(i);
                 }
                 mInfo.hdrCapabilities = mHdrCapabilities;
-                mInfo.appVsyncOffsetNanos = mode.appVsyncOffsetNanos;
-                mInfo.presentationDeadlineNanos = mode.presentationDeadlineNanos;
+                mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
+                mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
                 mInfo.state = mState;
                 mInfo.uniqueId = getUniqueId();
                 final DisplayAddress.Physical physicalAddress =
                         DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
                 mInfo.address = physicalAddress;
-                mInfo.densityDpi = (int) (mDisplayInfo.density * 160 + 0.5f);
-                mInfo.xDpi = mode.xDpi;
-                mInfo.yDpi = mode.yDpi;
-                mInfo.deviceProductInfo = mDisplayInfo.deviceProductInfo;
+                mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f);
+                mInfo.xDpi = mActiveSfDisplayMode.xDpi;
+                mInfo.yDpi = mActiveSfDisplayMode.yDpi;
+                mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
 
                 // Assume that all built-in displays that have secure output (eg. HDCP) also
                 // support compositing from gralloc protected buffers.
-                if (mDisplayInfo.secure) {
+                if (mStaticDisplayInfo.secure) {
                     mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
                             | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
                 }
@@ -620,7 +628,7 @@
                     }
                 }
 
-                if (mDisplayInfo.isInternal) {
+                if (mStaticDisplayInfo.isInternal) {
                     mInfo.type = Display.TYPE_INTERNAL;
                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
                     mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
@@ -878,9 +886,10 @@
             // Do not lock when calling these SurfaceControl methods because they are sync
             // operations that may block for a while when setting display power mode.
             SurfaceControl.setDesiredDisplayModeSpecs(displayToken, modeSpecs);
-            final int activeMode = SurfaceControl.getActiveDisplayMode(displayToken);
+            final int sfActiveModeId =
+                    SurfaceControl.getDynamicDisplayInfo(displayToken).activeDisplayModeId;
             synchronized (getSyncRoot()) {
-                if (updateActiveModeLocked(activeMode)) {
+                if (updateActiveModeLocked(sfActiveModeId)) {
                     updateDeviceInfoLocked();
                 }
             }
@@ -891,8 +900,8 @@
             updateDeviceInfoLocked();
         }
 
-        public void onActiveDisplayModeChangedLocked(int modeId) {
-            if (updateActiveModeLocked(modeId)) {
+        public void onActiveDisplayModeChangedLocked(int sfModeId) {
+            if (updateActiveModeLocked(sfModeId)) {
                 updateDeviceInfoLocked();
             }
         }
@@ -904,15 +913,15 @@
             }
         }
 
-        public boolean updateActiveModeLocked(int activeModeId) {
-            if (mActiveDisplayModeId == activeModeId) {
+        public boolean updateActiveModeLocked(int activeSfModeId) {
+            if (mActiveSfDisplayMode.id == activeSfModeId) {
                 return false;
             }
-            mActiveDisplayModeId = activeModeId;
-            mActiveModeId = findMatchingModeIdLocked(activeModeId);
+            mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
+            mActiveModeId = findMatchingModeIdLocked(activeSfModeId);
             if (mActiveModeId == NO_DISPLAY_MODE_ID) {
                 Slog.w(TAG, "In unknown mode after setting allowed modes"
-                        + ", activeModeId=" + mActiveDisplayModeId);
+                        + ", activeModeId=" + activeSfModeId);
             }
             return true;
         }
@@ -992,7 +1001,6 @@
             pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
             pw.println("mDisplayModeSpecs={" + mDisplayModeSpecs + "}");
             pw.println("mDisplayModeSpecsInvalid=" + mDisplayModeSpecsInvalid);
-            pw.println("mActiveDisplayModeId=" + mActiveDisplayModeId);
             pw.println("mActiveModeId=" + mActiveModeId);
             pw.println("mActiveColorMode=" + mActiveColorMode);
             pw.println("mDefaultModeId=" + mDefaultModeId);
@@ -1003,11 +1011,12 @@
             pw.println("mAllmRequested=" + mAllmRequested);
             pw.println("mGameContentTypeSupported=" + mGameContentTypeSupported);
             pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested);
-            pw.println("mDisplayInfo=" + mDisplayInfo);
-            pw.println("mDisplayModes=");
-            for (int i = 0; i < mDisplayModes.length; i++) {
-                pw.println("  " + mDisplayModes[i]);
+            pw.println("mStaticDisplayInfo=" + mStaticDisplayInfo);
+            pw.println("mSfDisplayModes=");
+            for (int i = 0; i < mSfDisplayModes.length; i++) {
+                pw.println("  " + mSfDisplayModes[i]);
             }
+            pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
             pw.println("mSupportedModes=");
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
@@ -1020,17 +1029,16 @@
             int matchingModeId = SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID;
             DisplayModeRecord record = mSupportedModes.get(modeId);
             if (record != null) {
-                for (int i = 0; i < mDisplayModes.length; i++) {
-                    SurfaceControl.DisplayMode mode = mDisplayModes[i];
+                for (SurfaceControl.DisplayMode mode : mSfDisplayModes) {
                     if (record.hasMatchingMode(mode)) {
                         if (matchingModeId
                                 == SurfaceControl.DisplayMode.INVALID_DISPLAY_MODE_ID) {
-                            matchingModeId = i;
+                            matchingModeId = mode.id;
                         }
 
                         // Prefer to return a mode that matches the modeGroup
                         if (mode.group == modeGroup) {
-                            return i;
+                            return mode.id;
                         }
                     }
                 }
@@ -1038,12 +1046,12 @@
             return matchingModeId;
         }
 
-        private int findMatchingModeIdLocked(int modeId) {
-            if (modeId < 0 || modeId >= mDisplayModes.length) {
-                Slog.e(TAG, "Invalid display config index " + modeId);
+        private int findMatchingModeIdLocked(int sfModeId) {
+            SurfaceControl.DisplayMode mode = getModeById(mSfDisplayModes, sfModeId);
+            if (mode == null) {
+                Slog.e(TAG, "Invalid display mode ID " + sfModeId);
                 return NO_DISPLAY_MODE_ID;
             }
-            SurfaceControl.DisplayMode mode = mDisplayModes[modeId];
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
                 if (record.hasMatchingMode(mode)) {
@@ -1229,8 +1237,6 @@
         /**
          * @param displayToken Token for display associated with this backlight.
          * @param isDefaultDisplay {@code true} if it is the default display.
-         * @param forceSurfaceControl {@code true} if brightness should always be
-         *                            set via SurfaceControl API.
          */
         BacklightAdapter(IBinder displayToken, boolean isDefaultDisplay) {
             mDisplayToken = displayToken;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 6deb19a..fd0b945 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -70,6 +70,7 @@
 import android.location.provider.ProviderProperties;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.os.ParcelFileDescriptor;
@@ -350,6 +351,22 @@
     void onSystemReady() {
         mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
                 this::onLocationModeChanged);
+
+        if (Build.IS_DEBUGGABLE) {
+            // on debug builds, watch for location noteOps while location is off. there are some
+            // scenarios (emergency location) where this is expected, but generally this should
+            // rarely occur, and may indicate bugs. dump occurrences to logs for further evaluation
+            AppOpsManager appOps = Objects.requireNonNull(
+                    mContext.getSystemService(AppOpsManager.class));
+            appOps.startWatchingNoted(
+                    new int[]{AppOpsManager.OP_FINE_LOCATION, AppOpsManager.OP_COARSE_LOCATION},
+                    (code, uid, packageName, attributionTag, flags, result) -> {
+                        if (!isLocationEnabledForUser(UserHandle.getUserId(uid))) {
+                            Log.w(TAG, "location noteOp with location off - "
+                                    + CallerIdentity.forTest(uid, 0, packageName, attributionTag));
+                        }
+                    });
+        }
     }
 
     void onSystemThirdPartyAppsCanStart() {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 6249a06..e1c011d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -46,6 +46,7 @@
 
 import com.android.server.location.ClientBrokerProto;
 
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -101,6 +102,11 @@
     private static final String TAG = "ContextHubClientBroker";
 
     /**
+     * Internal only authorization value used when the auth state is unknown.
+     */
+    private static final int AUTHORIZATION_UNKNOWN = -1;
+
+    /**
      * Message used by noteOp when this client receives a message from a nanoapp.
      */
     private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
@@ -227,7 +233,7 @@
                         if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
                             List<String> permissions = state.getNanoAppPermissions();
                             updateNanoAppAuthState(state.getNanoAppId(),
-                                    hasPermissions(permissions), false /* gracePeriodExpired */);
+                                    permissions, false /* gracePeriodExpired */);
                         }
                     }
                 }
@@ -344,32 +350,13 @@
     public int sendMessageToNanoApp(NanoAppMessage message) {
         ContextHubServiceUtil.checkPermissions(mContext);
 
-        int authState;
-        synchronized (mMessageChannelNanoappIdMap) {
-            // Default to the granted auth state. The true auth state will be checked async if it's
-            // not denied.
-            authState = mMessageChannelNanoappIdMap.getOrDefault(
-                    message.getNanoAppId(), AUTHORIZATION_GRANTED);
-            if (authState == AUTHORIZATION_DENIED) {
-                return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
-            }
-        }
-
         int result;
         if (isRegistered()) {
-            // Even though the auth state is currently not denied, query the nanoapp permissions
-            // async and verify that the host app currently holds all the requisite permissions.
-            // This can't be done synchronously due to the async query that needs to be performed to
-            // obtain the nanoapp permissions.
-            boolean initialNanoappMessage = false;
-            synchronized (mMessageChannelNanoappIdMap) {
-                if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
-                    mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
-                    initialNanoappMessage = true;
-                }
-            }
-
-            if (initialNanoappMessage) {
+            int authState = mMessageChannelNanoappIdMap.getOrDefault(
+                    message.getNanoAppId(), AUTHORIZATION_UNKNOWN);
+            if (authState == AUTHORIZATION_DENIED) {
+                return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+            } else if (authState == AUTHORIZATION_UNKNOWN) {
                 // Only check permissions the first time a nanoapp is queried since nanoapp
                 // permissions don't currently change at runtime. If the host permission changes
                 // later, that'll be checked by onOpChanged.
@@ -472,7 +459,8 @@
             List<String> messagePermissions) {
         long nanoAppId = message.getNanoAppId();
 
-        int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+        int authState = updateNanoAppAuthState(nanoAppId, nanoappPermissions,
+                false /* gracePeriodExpired */);
 
         // If in the grace period, the host may not receive any messages containing permissions
         // covered data.
@@ -482,7 +470,9 @@
             return;
         }
 
-        if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+        // If in the grace period, don't check permissions state since it'll cause cleanup
+        // messages to be dropped.
+        if (authState == AUTHORIZATION_DENIED
                 || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
             Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
                     + " doesn't have permission");
@@ -629,7 +619,8 @@
 
         if (timer != null) {
             updateNanoAppAuthState(
-                    nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+                    nanoAppId, Collections.emptyList() /* nanoappPermissions */,
+                    true /* gracePeriodExpired */);
         }
     }
 
@@ -643,24 +634,43 @@
         mTransactionManager.addTransaction(transaction);
     }
 
-    /**
-     * Updates the latest authentication state for this client to be able to communicate with the
-     * given nanoapp.
-     */
-    private void updateNanoAppAuthState(
-            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
-        updateNanoAppAuthState(
-                nanoAppId, hasPermissions, gracePeriodExpired, false /* forceDenied */);
+    private int updateNanoAppAuthState(
+            long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired) {
+        return updateNanoAppAuthState(
+                nanoAppId, nanoappPermissions, gracePeriodExpired, false /* forceDenied */);
     }
 
-    /* package */ void updateNanoAppAuthState(
-            long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired,
+    /**
+     * Updates the latest authenticatication state for the given nanoapp.
+     *
+     * @param nanoAppId the nanoapp that's auth state is being updated
+     * @param nanoappPermissions the Android permissions required to communicate with the nanoapp
+     * @param gracePeriodExpired indicates whether this invocation is a result of the grace period
+     *         expiring
+     * @param forceDenied indicates that no matter what auth state is asssociated with this nanoapp
+     *         it should transition to denied
+     * @return the latest auth state as of the completion of this method.
+     */
+    /* package */ int updateNanoAppAuthState(
+            long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired,
             boolean forceDenied) {
         int curAuthState;
         int newAuthState;
         synchronized (mMessageChannelNanoappIdMap) {
+            // Check permission granted state synchronously since this method can be invoked from
+            // multiple threads.
+            boolean hasPermissions = hasPermissions(nanoappPermissions);
+
             curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
-                    nanoAppId, AUTHORIZATION_GRANTED);
+                    nanoAppId, AUTHORIZATION_UNKNOWN);
+            if (curAuthState == AUTHORIZATION_UNKNOWN) {
+                // If there's never been an auth check performed, start the state as granted so the
+                // appropriate state transitions occur below and clients don't receive a granted
+                // callback if they're determined to be in the granted state initially.
+                curAuthState = AUTHORIZATION_GRANTED;
+                mMessageChannelNanoappIdMap.put(nanoAppId, AUTHORIZATION_GRANTED);
+            }
+
             newAuthState = curAuthState;
             // The below logic ensures that only the following transitions are possible:
             // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
@@ -701,6 +711,7 @@
             // Don't send the callback in the synchronized block or it could end up in a deadlock.
             sendAuthStateCallback(nanoAppId, newAuthState);
         }
+        return newAuthState;
     }
 
     private void sendAuthStateCallback(long nanoAppId, int authState) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 1b8f0de..81c1e45 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -942,8 +942,8 @@
         mClientManager.forEachClientOfHub(contextHubId, client -> {
             if (client.getPackageName().equals(packageName)) {
                 client.updateNanoAppAuthState(
-                        nanoAppId, false /* hasPermissions */, false /* gracePeriodExpired */,
-                        true /* forceDenied */);
+                        nanoAppId, Collections.emptyList() /* nanoappPermissions */,
+                        false /* gracePeriodExpired */, true /* forceDenied */);
             }
         });
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5cb9d8f..9e2ca9d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -781,7 +781,7 @@
     @GuardedBy("mLock")
     private File mInheritedFilesBase;
     @GuardedBy("mLock")
-    private boolean mVerityFound;
+    private boolean mVerityFoundForApks;
 
     /**
      * Both flags should be guarded with mLock whenever changes need to be in lockstep.
@@ -1010,9 +1010,14 @@
                 throw new IllegalArgumentException(
                         "DataLoader installation of APEX modules is not allowed.");
             }
+
             if (this.params.dataLoaderParams.getComponentName().getPackageName()
-                    == SYSTEM_DATA_LOADER_PACKAGE) {
-                assertShellOrSystemCalling("System data loaders");
+                    == SYSTEM_DATA_LOADER_PACKAGE && mContext.checkCallingOrSelfPermission(
+                    Manifest.permission.USE_SYSTEM_DATA_LOADERS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("You need the "
+                        + "com.android.permission.USE_SYSTEM_DATA_LOADERS permission "
+                        + "to use system data loaders");
             }
         }
 
@@ -2717,8 +2722,8 @@
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Missing existing base package");
         }
-        // Default to require only if existing base has fs-verity.
-        mVerityFound = PackageManagerServiceUtils.isApkVerityEnabled()
+        // Default to require only if existing base apk has fs-verity.
+        mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
                 && params.mode == SessionParams.MODE_INHERIT_EXISTING
                 && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath());
 
@@ -3013,34 +3018,18 @@
     }
 
     @GuardedBy("mLock")
-    private void maybeStageFsveritySignatureLocked(File origFile, File targetFile)
-            throws PackageManagerException {
+    private void maybeStageFsveritySignatureLocked(File origFile, File targetFile,
+            boolean fsVerityRequired) throws PackageManagerException {
         final File originalSignature = new File(
                 VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
-        // Make sure .fsv_sig exists when it should, then resolve and stage it.
         if (originalSignature.exists()) {
-            // mVerityFound can only change from false to true here during the staging loop. Since
-            // all or none of files should have .fsv_sig, this should only happen in the first time
-            // (or never), otherwise bail out.
-            if (!mVerityFound) {
-                mVerityFound = true;
-                if (mResolvedStagedFiles.size() > 1) {
-                    throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
-                            "Some file is missing fs-verity signature");
-                }
-            }
-        } else {
-            if (!mVerityFound) {
-                return;
-            }
+            final File stagedSignature = new File(
+                    VerityUtils.getFsveritySignatureFilePath(targetFile.getPath()));
+            stageFileLocked(originalSignature, stagedSignature);
+        } else if (fsVerityRequired) {
             throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
                     "Missing corresponding fs-verity signature to " + origFile);
         }
-
-        final File stagedSignature = new File(
-                VerityUtils.getFsveritySignatureFilePath(targetFile.getPath()));
-
-        stageFileLocked(originalSignature, stagedSignature);
     }
 
     @GuardedBy("mLock")
@@ -3059,7 +3048,11 @@
                 DexMetadataHelper.buildDexMetadataPathForApk(targetFile.getName()));
 
         stageFileLocked(dexMetadataFile, targetDexMetadataFile);
-        maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile);
+
+        // Also stage .dm.fsv_sig. .dm may be required to install with fs-verity signature on
+        // supported on older devices.
+        maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile,
+                VerityUtils.isFsVeritySupported() && DexMetadataHelper.isFsVerityRequired());
     }
 
     private void storeBytesToInstallationFile(final String localPath, final String absolutePath,
@@ -3121,13 +3114,45 @@
     }
 
     @GuardedBy("mLock")
+    private boolean isFsVerityRequiredForApk(File origFile, File targetFile)
+            throws PackageManagerException {
+        if (mVerityFoundForApks) {
+            return true;
+        }
+
+        // We haven't seen .fsv_sig for any APKs. Treat it as not required until we see one.
+        final File originalSignature = new File(
+                VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
+        if (!originalSignature.exists()) {
+            return false;
+        }
+        mVerityFoundForApks = true;
+
+        // When a signature is found, also check any previous staged APKs since they also need to
+        // have fs-verity signature consistently.
+        for (File file : mResolvedStagedFiles) {
+            if (!file.getName().endsWith(".apk")) {
+                continue;
+            }
+            // Ignore the current targeting file.
+            if (targetFile.getName().equals(file.getName())) {
+                continue;
+            }
+            throw new PackageManagerException(INSTALL_FAILED_BAD_SIGNATURE,
+                    "Previously staged apk is missing fs-verity signature");
+        }
+        return true;
+    }
+
+    @GuardedBy("mLock")
     private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName)
             throws PackageManagerException {
         stageFileLocked(origFile, targetFile);
 
-        // Stage fsverity signature if present.
-        maybeStageFsveritySignatureLocked(origFile, targetFile);
-        // Stage dex metadata (.dm) if present.
+        // Stage APK's fs-verity signature if present.
+        maybeStageFsveritySignatureLocked(origFile, targetFile,
+                isFsVerityRequiredForApk(origFile, targetFile));
+        // Stage dex metadata (.dm) and corresponding fs-verity signature if present.
         maybeStageDexMetadataLocked(origFile, targetFile);
         // Stage checksums (.digests) if present.
         maybeStageDigestsLocked(origFile, targetFile, splitName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 14d15ac..2a3feb1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -316,7 +316,6 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.LogPrinter;
-import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.MathUtils;
 import android.util.PackageUtils;
@@ -401,6 +400,7 @@
 import com.android.server.utils.Watchable;
 import com.android.server.utils.Watched;
 import com.android.server.utils.WatchedArrayMap;
+import com.android.server.utils.WatchedLongSparseArray;
 import com.android.server.utils.WatchedSparseBooleanArray;
 import com.android.server.utils.Watcher;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -1402,10 +1402,10 @@
 
     // Currently known shared libraries.
     @Watched
-    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries =
-            new WatchedArrayMap<>();
+    final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+            mSharedLibraries = new WatchedArrayMap<>();
     @Watched
-    final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+    final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
             mStaticLibsByDeclaringPackage = new WatchedArrayMap<>();
 
     // Mapping from instrumentation class names to info about them.
@@ -1756,6 +1756,11 @@
         public boolean filterAppAccess(String packageName, int callingUid, int userId) {
             return mPmInternal.filterAppAccess(packageName, callingUid, userId);
         }
+
+        @Override
+        public int[] getAllUserIds() {
+            return mUserManager.getUserIds();
+        }
     }
 
     /**
@@ -1786,8 +1791,8 @@
         public final Settings settings;
         public final SparseIntArray isolatedOwners;
         public final WatchedArrayMap<String, AndroidPackage> packages;
-        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> sharedLibs;
-        public final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>> staticLibs;
+        public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibs;
+        public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> staticLibs;
         public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation;
         public final WatchedSparseBooleanArray webInstantAppsDisabled;
         public final ComponentName resolveComponentName;
@@ -2005,9 +2010,9 @@
         private final WatchedArrayMap<String, AndroidPackage> mPackages;
         private final WatchedArrayMap<ComponentName, ParsedInstrumentation>
                 mInstrumentation;
-        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+        private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
                 mStaticLibsByDeclaringPackage;
-        private final WatchedArrayMap<String, LongSparseArray<SharedLibraryInfo>>
+        private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
                 mSharedLibraries;
         private final ComponentName mLocalResolveComponentName;
         private final ActivityInfo mResolveActivity;
@@ -3546,7 +3551,7 @@
             packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
 
             // Is this a static library?
-            LongSparseArray<SharedLibraryInfo> versionedLib =
+            WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
                     mStaticLibsByDeclaringPackage.get(packageName);
             if (versionedLib == null || versionedLib.size() <= 0) {
                 return packageName;
@@ -4701,6 +4706,10 @@
     // and an image with the flag set false does not use snapshots.
     private static final boolean SNAPSHOT_ENABLED = true;
 
+    // The per-instance snapshot disable/enable flag.  This is generally set to false in
+    // test instances and set to SNAPSHOT_ENABLED in operational instances.
+    private final boolean mSnapshotEnabled;
+
     /**
      * Return the live computer.
      */
@@ -4713,7 +4722,7 @@
      * The live computer will be returned if snapshots are disabled.
      */
     private Computer snapshotComputer() {
-        if (!SNAPSHOT_ENABLED) {
+        if (!mSnapshotEnabled) {
             return mLiveComputer;
         }
         if (Thread.holdsLock(mLock)) {
@@ -6048,15 +6057,12 @@
         mOverlayConfigSignaturePackage = testParams.overlayConfigSignaturePackage;
         mResolveComponentName = testParams.resolveComponentName;
 
-        // Create the computer as soon as the state objects have been installed.  The
-        // cached computer is the same as the live computer until the end of the
-        // constructor, at which time the invalidation method updates it.  The cache is
-        // corked initially to ensure a cached computer is not built until the end of the
-        // constructor.
-        sSnapshotCorked = true;
+        // Disable snapshots in this instance of PackageManagerService, which is only used
+        // for testing.  The instance still needs a live computer.  The snapshot computer
+        // is set to null since it must never be used by this instance.
+        mSnapshotEnabled = false;
         mLiveComputer = createLiveComputer();
-        mSnapshotComputer = mLiveComputer;
-        registerObserver();
+        mSnapshotComputer = null;
 
         mPackages.putAll(testParams.packages);
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6069,7 +6075,6 @@
         mIncrementalVersion = testParams.incrementalVersion;
 
         invalidatePackageInfoCache();
-        sSnapshotCorked = false;
     }
 
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest,
@@ -6215,6 +6220,7 @@
         // constructor, at which time the invalidation method updates it.  The cache is
         // corked initially to ensure a cached computer is not built until the end of the
         // constructor.
+        mSnapshotEnabled = SNAPSHOT_ENABLED;
         sSnapshotCorked = true;
         mLiveComputer = createLiveComputer();
         mSnapshotComputer = mLiveComputer;
@@ -8038,7 +8044,7 @@
             final int[] allUsers = mUserManager.getUserIds();
             final int libCount = mSharedLibraries.size();
             for (int i = 0; i < libCount; i++) {
-                final LongSparseArray<SharedLibraryInfo> versionedLib
+                final WatchedLongSparseArray<SharedLibraryInfo> versionedLib
                         = mSharedLibraries.valueAt(i);
                 if (versionedLib == null) {
                     continue;
@@ -8303,7 +8309,8 @@
 
             final int libCount = mSharedLibraries.size();
             for (int i = 0; i < libCount; i++) {
-                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.valueAt(i);
+                WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
+                        mSharedLibraries.valueAt(i);
                 if (versionedLib == null) {
                     continue;
                 }
@@ -8372,7 +8379,8 @@
 
             int libraryCount = mSharedLibraries.size();
             for (int i = 0; i < libraryCount; i++) {
-                LongSparseArray<SharedLibraryInfo> versionedLibrary = mSharedLibraries.valueAt(i);
+                WatchedLongSparseArray<SharedLibraryInfo> versionedLibrary =
+                        mSharedLibraries.valueAt(i);
                 if (versionedLibrary == null) {
                     continue;
                 }
@@ -8533,7 +8541,8 @@
             Set<String> libs = null;
             final int libCount = mSharedLibraries.size();
             for (int i = 0; i < libCount; i++) {
-                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.valueAt(i);
+                WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
+                        mSharedLibraries.valueAt(i);
                 if (versionedLib == null) {
                     continue;
                 }
@@ -8959,6 +8968,10 @@
 
     @Override
     public List<String> getAllPackages() {
+        // Allow iorapd to call this method.
+        if (Binder.getCallingUid() != Process.IORAPD_UID) {
+            enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers");
+        }
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
         synchronized (mLock) {
@@ -9837,7 +9850,7 @@
 
     private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, int userId) {
-        return liveComputer().queryIntentActivitiesInternal(intent,
+        return snapshotComputer().queryIntentActivitiesInternal(intent,
                 resolvedType, flags, userId);
     }
 
@@ -10324,7 +10337,7 @@
     private @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
-        return liveComputer().queryIntentServicesInternal(intent,
+        return snapshotComputer().queryIntentServicesInternal(intent,
                 resolvedType, flags, userId, callingUid,
                 includeInstantApps);
     }
@@ -12198,10 +12211,10 @@
 
     @Nullable
     private static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
-            Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries) {
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+            @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
         if (newLibraries != null) {
-            final LongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
+            final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
             SharedLibraryInfo info = null;
             if (versionedLib != null) {
                 info = versionedLib.get(version);
@@ -12210,7 +12223,7 @@
                 return info;
             }
         }
-        final LongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
+        final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
         if (versionedLib == null) {
             return null;
         }
@@ -12218,7 +12231,7 @@
     }
 
     private SharedLibraryInfo getLatestSharedLibraVersionLPr(AndroidPackage pkg) {
-        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                 pkg.getStaticSharedLibName());
         if (versionedLib == null) {
             return null;
@@ -12523,8 +12536,8 @@
 
     private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
             Map<String, AndroidPackage> availablePackages,
-            @NonNull final Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable final Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries)
+            @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
             throws PackageManagerException {
         if (pkg == null) {
             return null;
@@ -12621,8 +12634,8 @@
             @NonNull String packageName, boolean required, int targetSdk,
             @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
-            @NonNull final Map<String, LongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable final Map<String, LongSparseArray<SharedLibraryInfo>> newLibraries)
+            @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++) {
@@ -14043,7 +14056,7 @@
                 long minVersionCode = Long.MIN_VALUE;
                 long maxVersionCode = Long.MAX_VALUE;
 
-                LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
+                WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(
                         pkg.getStaticSharedLibName());
                 if (versionedLib != null) {
                     final int versionCount = versionedLib.size();
@@ -14271,8 +14284,8 @@
     }
 
     private static boolean sharedLibExists(final String name, final long version,
-            Map<String, LongSparseArray<SharedLibraryInfo>> librarySource) {
-        LongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
         if (versionedLib != null && versionedLib.indexOfKey(version) >= 0) {
             return true;
         }
@@ -14282,9 +14295,9 @@
     @GuardedBy("mLock")
     private void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) {
         final String name = libraryInfo.getName();
-        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
         if (versionedLib == null) {
-            versionedLib = new LongSparseArray<>();
+            versionedLib = new WatchedLongSparseArray<>();
             mSharedLibraries.put(name, versionedLib);
         }
         final String declaringPackageName = libraryInfo.getDeclaringPackage().getPackageName();
@@ -14295,7 +14308,7 @@
     }
 
     private boolean removeSharedLibraryLPw(String name, long version) {
-        LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name);
         if (versionedLib == null) {
             return false;
         }
@@ -18290,7 +18303,7 @@
         public final Map<String, ScanResult> scannedPackages;
 
         public final Map<String, AndroidPackage> allPackages;
-        public final Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource;
+        public final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource;
         public final Map<String, InstallArgs> installArgs;
         public final Map<String, PackageInstalledInfo> installResults;
         public final Map<String, PrepareResult> preparedPackages;
@@ -18301,7 +18314,7 @@
                 Map<String, InstallArgs> installArgs,
                 Map<String, PackageInstalledInfo> installResults,
                 Map<String, PrepareResult> preparedPackages,
-                Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
+                Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                 Map<String, AndroidPackage> allPackages,
                 Map<String, VersionInfo> versionInfos,
                 Map<String, PackageSetting> lastStaticSharedLibSettings) {
@@ -18316,7 +18329,7 @@
         }
 
         private ReconcileRequest(Map<String, ScanResult> scannedPackages,
-                Map<String, LongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
+                Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
                 Map<String, AndroidPackage> allPackages,
                 Map<String, VersionInfo> versionInfos,
                 Map<String, PackageSetting> lastStaticSharedLibSettings) {
@@ -18413,7 +18426,7 @@
 
         combinedPackages.putAll(request.allPackages);
 
-        final Map<String, LongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
+        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
                 new ArrayMap<>();
 
         for (String installPackageName : scannedPackages.keySet()) {
@@ -18637,7 +18650,7 @@
      */
     private static List<SharedLibraryInfo> getAllowedSharedLibInfos(
             ScanResult scanResult,
-            Map<String, LongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
         // Let's used the parsed package as scanResult.pkgSetting may be null
         final ParsedPackage parsedPackage = scanResult.request.parsedPackage;
         if (scanResult.staticSharedLibraryInfo == null
@@ -18707,7 +18720,7 @@
      * added.
      */
     private static boolean addSharedLibraryToPackageVersionMap(
-            Map<String, LongSparseArray<SharedLibraryInfo>> target,
+            Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
             SharedLibraryInfo library) {
         final String name = library.getName();
         if (target.containsKey(name)) {
@@ -18719,7 +18732,7 @@
                 return false;
             }
         } else {
-            target.put(name, new LongSparseArray<>());
+            target.put(name, new WatchedLongSparseArray<>());
         }
         target.get(name).put(library.getLongVersion(), library);
         return true;
@@ -23842,7 +23855,7 @@
                 final int numSharedLibraries = mSharedLibraries.size();
                 for (int index = 0; index < numSharedLibraries; index++) {
                     final String libName = mSharedLibraries.keyAt(index);
-                    LongSparseArray<SharedLibraryInfo> versionedLib
+                    WatchedLongSparseArray<SharedLibraryInfo> versionedLib
                             = mSharedLibraries.get(libName);
                     if (versionedLib == null) {
                         continue;
@@ -24191,7 +24204,7 @@
         final int count = mSharedLibraries.size();
         for (int i = 0; i < count; i++) {
             final String libName = mSharedLibraries.keyAt(i);
-            LongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
+            WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName);
             if (versionedLib == null) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 5d4370a..b6ea901 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -358,5 +358,8 @@
 
         @Nullable
         AndroidPackage getPackageLocked(@NonNull String pkgName);
+
+        @UserIdInt
+        int[] getAllUserIds();
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index e5ed774..8e5aead 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -423,12 +423,8 @@
                 for (int pkgStateIndex = 0; pkgStateIndex < pkgStateSize; pkgStateIndex++) {
                     DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
                     if (userId == UserHandle.USER_ALL) {
-                        SparseArray<DomainVerificationUserState> userStates =
-                                pkgState.getUserSelectionStates();
-                        int userStatesSize = userStates.size();
-                        for (int userStateIndex = 0; userStateIndex < userStatesSize;
-                                userStateIndex++) {
-                            userStates.valueAt(userStateIndex)
+                        for (int aUserId : mConnection.getAllUserIds()) {
+                            pkgState.getOrCreateUserSelectionState(aUserId)
                                     .setLinkHandlingAllowed(allowed);
                         }
                     } else {
@@ -436,7 +432,6 @@
                                 .setLinkHandlingAllowed(allowed);
                     }
                 }
-
             }
         } else {
             synchronized (mLock) {
@@ -500,28 +495,33 @@
 
     @Override
     public void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
-            @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+            @Nullable String packageName, boolean enabled, @Nullable ArraySet<String> domains)
             throws NameNotFoundException {
         mEnforcer.assertInternal(mConnection.getCallingUid());
 
+
         if (packageName == null) {
             synchronized (mLock) {
                 Set<String> validDomains = new ArraySet<>();
-
                 int size = mAttachedPkgStates.size();
                 for (int index = 0; index < size; index++) {
                     DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
                     String pkgName = pkgState.getPackageName();
                     PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
-                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                    AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+                    if (pkg == null) {
                         continue;
                     }
 
-                    validDomains.clear();
-                    validDomains.addAll(domains);
+                    if (domains == null) {
+                        validDomains = mCollector.collectAllWebDomains(pkg);
+                    } else {
+                        validDomains.clear();
+                        validDomains.addAll(domains);
+                    }
 
                     setDomainVerificationUserSelectionInternal(userId, pkgState,
-                            pkgSetting.getPkg(), enabled, validDomains);
+                            pkg, enabled, validDomains);
                 }
             }
         } else {
@@ -532,12 +532,16 @@
                 }
 
                 PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
-                if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+                if (pkg == null) {
                     throw DomainVerificationUtils.throwPackageUnavailable(packageName);
                 }
 
+                Set<String> validDomains =
+                        domains == null ? mCollector.collectAllWebDomains(pkg) : domains;
+
                 setDomainVerificationUserSelectionInternal(userId, pkgState, pkgSetting.getPkg(),
-                        enabled, domains);
+                        enabled, validDomains);
             }
         }
 
@@ -549,12 +553,10 @@
             boolean enabled, Set<String> domains) {
         domains.retainAll(mCollector.collectAllWebDomains(pkg));
 
-        SparseArray<DomainVerificationUserState> userStates =
-                pkgState.getUserSelectionStates();
         if (userId == UserHandle.USER_ALL) {
-            int size = userStates.size();
-            for (int index = 0; index < size; index++) {
-                DomainVerificationUserState userState = userStates.valueAt(index);
+            for (int aUserId : mConnection.getAllUserIds()) {
+                DomainVerificationUserState userState =
+                        pkgState.getOrCreateUserSelectionState(aUserId);
                 if (enabled) {
                     userState.addHosts(domains);
                 } else {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 7f9e75a..d083d11 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -250,6 +250,10 @@
             return false;
         }
 
+        if (domains.size() == 1 && domains.contains("all")) {
+            domains = null;
+        }
+
         try {
             mCallback.setDomainVerificationUserSelectionInternal(userId,
                     packageName, enabled, domains);
@@ -446,10 +450,10 @@
          * @param packageName the package whose state to change, or all packages if non is
          *                    specified
          * @param enabled     whether the domain is now approved by the user
-         * @param domains     the set of domains to change
+         * @param domains     the set of domains to change, or null to affect all domains
          */
         void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
-                @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+                @Nullable String packageName, boolean enabled, @Nullable ArraySet<String> domains)
                 throws PackageManager.NameNotFoundException;
 
         /**
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
new file mode 100644
index 0000000..2442079
--- /dev/null
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -0,0 +1,456 @@
+/*
+ * 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.power;
+
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Class used to detect when the phone is placed face down. This is used for Flip to Screen Off. A
+ * client can use this detector to trigger state changes like screen off when the phone is face
+ * down.
+ */
+public class FaceDownDetector implements SensorEventListener {
+
+    private static final String TAG = "FaceDownDetector";
+    private static final boolean DEBUG = false;
+
+    private static final int SCREEN_OFF_RESULT =
+            FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__SCREEN_OFF;
+    private static final int USER_INTERACTION =
+            FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__USER_INTERACTION;
+    private static final int UNFLIP =
+            FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__UNFLIP;
+
+    /**
+     * Used by the ExponentialMovingAverage accelerations, this determines how quickly the
+     * average can change. A number closer to 1 will mean it will take longer to change.
+     */
+    private static final float MOVING_AVERAGE_WEIGHT = 0.5f;
+
+    /** DeviceConfig flag name, if {@code true}, enables Face Down features. */
+    private static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_FEATURE_ENABLED = true;
+
+    private boolean mIsEnabled;
+
+    /**
+     * DeviceConfig flag name, determines how long to disable sensor when user interacts while
+     * device is flipped.
+     */
+    private static final String KEY_INTERACTION_BACKOFF = "face_down_interaction_backoff_millis";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final long DEFAULT_INTERACTION_BACKOFF = 60_000;
+
+    private long mUserInteractionBackoffMillis;
+
+    /**
+     * DeviceConfig flag name, defines the max change in acceleration which will prevent face down
+     * due to movement.
+     */
+    static final String KEY_ACCELERATION_THRESHOLD = "acceleration_threshold";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    static final float DEFAULT_ACCELERATION_THRESHOLD = 0.2f;
+
+    private float mAccelerationThreshold;
+
+    /**
+     * DeviceConfig flag name, defines the maximum z-axis acceleration that will indicate the phone
+     * is face down.
+     */
+    static final String KEY_Z_ACCELERATION_THRESHOLD = "z_acceleration_threshold";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    static final float DEFAULT_Z_ACCELERATION_THRESHOLD = -9.5f;
+
+    private float mZAccelerationThreshold;
+
+    /**
+     * After going face down, we relax the threshold to make it more difficult to exit face down
+     * than to enter it.
+     */
+    private float mZAccelerationThresholdLenient;
+
+    /**
+     * DeviceConfig flag name, defines the minimum amount of time that has to pass while the phone
+     * is face down and not moving in order to trigger face down behavior, in milliseconds.
+     */
+    static final String KEY_TIME_THRESHOLD_MILLIS = "time_threshold_millis";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    static final long DEFAULT_TIME_THRESHOLD_MILLIS = 1_000L;
+
+    private Duration mTimeThreshold;
+
+    private Sensor mAccelerometer;
+    private SensorManager mSensorManager;
+    private final Consumer<Boolean> mOnFlip;
+
+    /** Values we store for logging purposes. */
+    private long mLastFlipTime = 0L;
+    public int mPreviousResultType = 0;
+    public long mPreviousResultTime = 0L;
+    private long mMillisSaved = 0L;
+
+    private final ExponentialMovingAverage mCurrentXYAcceleration =
+            new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT);
+    private final ExponentialMovingAverage mCurrentZAcceleration =
+            new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT);
+
+    private boolean mFaceDown = false;
+    private boolean mActive = false;
+
+    private float mPrevAcceleration = 0;
+    private long mPrevAccelerationTime = 0;
+
+    private boolean mZAccelerationIsFaceDown = false;
+    private long mZAccelerationFaceDownTime = 0L;
+
+    private final Handler mHandler;
+    private final Runnable mUserActivityRunnable;
+
+    public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) {
+        mOnFlip = Objects.requireNonNull(onFlip);
+        mHandler = new Handler(Looper.getMainLooper());
+        mUserActivityRunnable = () -> {
+            if (mFaceDown) {
+                exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime);
+                checkAndUpdateActiveState(false);
+            }
+        };
+    }
+
+    /** Initializes the FaceDownDetector and all necessary listeners. */
+    public void systemReady(Context context) {
+        mSensorManager = context.getSystemService(SensorManager.class);
+        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        readValuesFromDeviceConfig();
+        checkAndUpdateActiveState(true);
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                ActivityThread.currentApplication().getMainExecutor(),
+                (properties) -> onDeviceConfigChange(properties.getKeyset()));
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        context.registerReceiver(new ScreenStateReceiver(), intentFilter);
+    }
+
+    /**
+     * Sets the active state of the detector. If false, we will not process accelerometer changes.
+     */
+    private void checkAndUpdateActiveState(boolean active) {
+        if (mIsEnabled && mActive != active) {
+            final long currentTime = SystemClock.uptimeMillis();
+            // Don't make active if there was recently a user interaction while face down.
+            if (active && mPreviousResultType == USER_INTERACTION
+                    && currentTime - mPreviousResultTime  < mUserInteractionBackoffMillis) {
+                return;
+            }
+            if (DEBUG) Slog.d(TAG, "Update active - " + active);
+            mActive = active;
+            if (!active) {
+                if (mFaceDown && mPreviousResultTime != USER_INTERACTION) {
+                    mPreviousResultType = SCREEN_OFF_RESULT;
+                    mPreviousResultTime = currentTime;
+                }
+                mSensorManager.unregisterListener(this);
+                mFaceDown = false;
+                mOnFlip.accept(false);
+            } else {
+                if (mPreviousResultType == SCREEN_OFF_RESULT) {
+                    logScreenOff();
+                }
+                mSensorManager.registerListener(
+                        this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
+            }
+        }
+    }
+
+    /** Prints state information about FaceDownDetector */
+    public void dump(PrintWriter pw) {
+        pw.println("FaceDownDetector:");
+        pw.println("  mFaceDown=" + mFaceDown);
+        pw.println("  mActive=" + mActive);
+        pw.println("  mLastFlipTime=" + mLastFlipTime);
+        pw.println("  mUserInteractionBackoffMillis=" + mUserInteractionBackoffMillis);
+        pw.println("  mPreviousResultTime=" + mPreviousResultTime);
+        pw.println("  mPreviousResultType=" + mPreviousResultType);
+        pw.println("  mMillisSaved=" + mMillisSaved);
+        pw.println("  mZAccelerationThreshold=" + mZAccelerationThreshold);
+        pw.println("  mAccelerationThreshold=" + mAccelerationThreshold);
+        pw.println("  mTimeThreshold=" + mTimeThreshold);
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) return;
+        if (!mActive || !mIsEnabled) return;
+
+        final float x = event.values[0];
+        final float y = event.values[1];
+        mCurrentXYAcceleration.updateMovingAverage(x * x + y * y);
+        mCurrentZAcceleration.updateMovingAverage(event.values[2]);
+
+        // Detect movement
+        // If the x, y acceleration is within the acc threshold for at least a length of time longer
+        // than the time threshold, we set moving to true.
+        final long curTime = event.timestamp;
+        if (Math.abs(mCurrentXYAcceleration.mMovingAverage - mPrevAcceleration)
+                > mAccelerationThreshold) {
+            mPrevAcceleration = mCurrentXYAcceleration.mMovingAverage;
+            mPrevAccelerationTime = curTime;
+        }
+        final boolean moving = curTime - mPrevAccelerationTime <= mTimeThreshold.toNanos();
+
+        // If the z acceleration is beyond the gravity/z-acceleration threshold for at least a
+        // length of time longer than the time threshold, we set isFaceDownForPeriod to true.
+        final float zAccelerationThreshold =
+                mFaceDown ? mZAccelerationThresholdLenient : mZAccelerationThreshold;
+        final boolean isCurrentlyFaceDown =
+                mCurrentZAcceleration.mMovingAverage < zAccelerationThreshold;
+        final boolean isFaceDownForPeriod = isCurrentlyFaceDown
+                && mZAccelerationIsFaceDown
+                && curTime - mZAccelerationFaceDownTime > mTimeThreshold.toNanos();
+        if (isCurrentlyFaceDown && !mZAccelerationIsFaceDown) {
+            mZAccelerationFaceDownTime = curTime;
+            mZAccelerationIsFaceDown = true;
+        } else if (!isCurrentlyFaceDown) {
+            mZAccelerationIsFaceDown = false;
+        }
+
+
+        if (!moving && isFaceDownForPeriod && !mFaceDown) {
+            faceDownDetected();
+        } else if (!isFaceDownForPeriod && mFaceDown) {
+            unFlipDetected();
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+
+    private void faceDownDetected() {
+        if (DEBUG) Slog.d(TAG, "Triggered faceDownDetected.");
+        mLastFlipTime = SystemClock.uptimeMillis();
+        mFaceDown = true;
+        mOnFlip.accept(true);
+    }
+
+    private void unFlipDetected() {
+        if (DEBUG) Slog.d(TAG, "Triggered exitFaceDown");
+        exitFaceDown(UNFLIP, SystemClock.uptimeMillis() - mLastFlipTime);
+    }
+
+    /**
+     * The user interacted with the screen while face down, indicated the phone is in use.
+     * We log this event and temporarily make this detector inactive.
+     */
+    public void userActivity() {
+        mHandler.post(mUserActivityRunnable);
+    }
+
+    private void exitFaceDown(int resultType, long millisSinceFlip) {
+        FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED,
+                resultType,
+                millisSinceFlip,
+                /* millis_until_normal_timeout= */ 0L,
+                /* millis_until_next_screen_on= */ 0L);
+        mFaceDown = false;
+        mLastFlipTime = 0L;
+        mPreviousResultType = resultType;
+        mPreviousResultTime = SystemClock.uptimeMillis();
+        mOnFlip.accept(false);
+    }
+
+    private void logScreenOff() {
+        if (mPreviousResultType == SCREEN_OFF_RESULT) {
+            final long currentTime = SystemClock.uptimeMillis();
+            FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED,
+                    mPreviousResultType,
+                    /* millis_since_flip= */ mPreviousResultTime  - mLastFlipTime,
+                    mMillisSaved,
+                    /* millis_until_next_screen_on= */ currentTime - mPreviousResultTime);
+            mPreviousResultType = -1;
+        }
+    }
+
+    private boolean isEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
+                DEFAULT_FEATURE_ENABLED);
+    }
+
+    private float getAccelerationThreshold() {
+        return getFloatFlagValue(KEY_ACCELERATION_THRESHOLD,
+                DEFAULT_ACCELERATION_THRESHOLD,
+                -2.0f,
+                2.0f);
+    }
+
+    private float getZAccelerationThreshold() {
+        return getFloatFlagValue(KEY_Z_ACCELERATION_THRESHOLD,
+                DEFAULT_Z_ACCELERATION_THRESHOLD,
+                -15.0f,
+                0.0f);
+    }
+
+    private long getUserInteractionBackoffMillis() {
+        return getLongFlagValue(KEY_INTERACTION_BACKOFF,
+                DEFAULT_INTERACTION_BACKOFF,
+                0,
+                3600_000);
+    }
+
+    private float getFloatFlagValue(String key, float defaultValue, float min, float max) {
+        final float value = DeviceConfig.getFloat(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                key,
+                defaultValue);
+
+        if (value < min || value > max) {
+            Slog.w(TAG, "Bad flag value supplied for: " + key);
+            return defaultValue;
+        }
+
+        return value;
+    }
+
+    private long getLongFlagValue(String key, long defaultValue, long min, long max) {
+        final long value = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                key,
+                defaultValue);
+
+        if (value < min || value > max) {
+            Slog.w(TAG, "Bad flag value supplied for: " + key);
+            return defaultValue;
+        }
+
+        return value;
+    }
+
+    private Duration getTimeThreshold() {
+        final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_TIME_THRESHOLD_MILLIS,
+                DEFAULT_TIME_THRESHOLD_MILLIS);
+
+        if (millis < 0 || millis > 15_000) {
+            Slog.w(TAG, "Bad flag value supplied for: " + KEY_TIME_THRESHOLD_MILLIS);
+            return Duration.ofMillis(DEFAULT_TIME_THRESHOLD_MILLIS);
+        }
+
+        return Duration.ofMillis(millis);
+    }
+
+    private void onDeviceConfigChange(@NonNull Set<String> keys) {
+        for (String key : keys) {
+            switch (key) {
+                case KEY_ACCELERATION_THRESHOLD:
+                case KEY_Z_ACCELERATION_THRESHOLD:
+                case KEY_TIME_THRESHOLD_MILLIS:
+                case KEY_FEATURE_ENABLED:
+                    readValuesFromDeviceConfig();
+                    return;
+                default:
+                    Slog.i(TAG, "Ignoring change on " + key);
+            }
+        }
+    }
+
+    private void readValuesFromDeviceConfig() {
+        mAccelerationThreshold = getAccelerationThreshold();
+        mZAccelerationThreshold = getZAccelerationThreshold();
+        mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f;
+        mTimeThreshold = getTimeThreshold();
+        mIsEnabled = isEnabled();
+        mUserInteractionBackoffMillis = getUserInteractionBackoffMillis();
+
+        Slog.i(TAG, "readValuesFromDeviceConfig():"
+                + "\nmAccelerationThreshold=" + mAccelerationThreshold
+                + "\nmZAccelerationThreshold=" + mZAccelerationThreshold
+                + "\nmTimeThreshold=" + mTimeThreshold
+                + "\nmIsEnabled=" + mIsEnabled);
+    }
+
+    /**
+     * Sets how much screen on time might be saved as a result of this detector. Currently used for
+     * logging purposes.
+     */
+    public void setMillisSaved(long millisSaved) {
+        mMillisSaved = millisSaved;
+    }
+
+    private final class ScreenStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                checkAndUpdateActiveState(false);
+            } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+                checkAndUpdateActiveState(true);
+            }
+        }
+    }
+
+    private final class ExponentialMovingAverage {
+        private final float mAlpha;
+        private final float mInitialAverage;
+        private float mMovingAverage;
+
+        ExponentialMovingAverage(float alpha) {
+            this(alpha, 0.0f);
+        }
+
+        ExponentialMovingAverage(float alpha, float initialAverage) {
+            this.mAlpha = alpha;
+            this.mInitialAverage = initialAverage;
+            this.mMovingAverage = initialAverage;
+        }
+
+        void updateMovingAverage(float newValue) {
+            mMovingAverage = newValue + mAlpha * (mMovingAverage - newValue);
+        }
+
+        void reset() {
+            mMovingAverage = this.mInitialAverage;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index b8e0156..f49e2f1 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -119,6 +119,7 @@
     private final AppOpsManager mAppOps;
     private final SuspendBlocker mSuspendBlocker;
     private final WindowManagerPolicy mPolicy;
+    private final FaceDownDetector mFaceDownDetector;
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
@@ -165,12 +166,14 @@
     private boolean mUserActivityPending;
 
     public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
-            SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+            SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+            FaceDownDetector faceDownDetector) {
         mContext = context;
         mBatteryStats = batteryStats;
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mSuspendBlocker = suspendBlocker;
         mPolicy = policy;
+        mFaceDownDetector = faceDownDetector;
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -654,6 +657,7 @@
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         tm.notifyUserActivity();
         mPolicy.userActivity();
+        mFaceDownDetector.userActivity();
     }
 
     void postEnhancedDischargePredictionBroadcast(long delayMs) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index c0b8202..a135705 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -176,6 +176,8 @@
     private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
     // Dirty bit: attentive timer may have timed out
     private static final int DIRTY_ATTENTIVE = 1 << 14;
+    // Dirty bit: phone flipped to face down
+    private static final int DIRTY_FACE_DOWN = 1 << 15;
 
     // Summarizes the state of all active wakelocks.
     private static final int WAKE_LOCK_CPU = 1 << 0;
@@ -263,6 +265,7 @@
     private final BatterySaverStateMachine mBatterySaverStateMachine;
     private final BatterySavingStats mBatterySavingStats;
     private final AttentionDetector mAttentionDetector;
+    private final FaceDownDetector mFaceDownDetector;
     private final BinderService mBinderService;
     private final LocalService mLocalService;
     private final NativeWrapper mNativeWrapper;
@@ -547,6 +550,10 @@
     public final float mScreenBrightnessMaximumVr;
     public final float mScreenBrightnessDefaultVr;
 
+    // Value we store for tracking face down behavior.
+    private boolean mIsFaceDown = false;
+    private long mLastFlipTime = 0L;
+
     // The screen brightness mode.
     // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
     private int mScreenBrightnessModeSetting;
@@ -791,8 +798,10 @@
     @VisibleForTesting
     static class Injector {
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
-                SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
-            return new Notifier(looper, context, batteryStats, suspendBlocker, policy);
+                SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                FaceDownDetector faceDownDetector) {
+            return new Notifier(
+                    looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
         }
 
         SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -906,6 +915,7 @@
         mAmbientDisplaySuppressionController =
                 mInjector.createAmbientDisplaySuppressionController(context);
         mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
+        mFaceDownDetector = new FaceDownDetector(this::onFlip);
 
         mBatterySavingStats = new BatterySavingStats(mLock);
         mBatterySaverPolicy =
@@ -1011,6 +1021,26 @@
         }
     }
 
+    private void onFlip(boolean isFaceDown) {
+        long millisUntilNormalTimeout = 0;
+        synchronized (mLock) {
+            mIsFaceDown = isFaceDown;
+            if (isFaceDown) {
+                final long currentTime = mClock.uptimeMillis();
+                mLastFlipTime = currentTime;
+                final long sleepTimeout = getSleepTimeoutLocked(-1L);
+                final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, -1L);
+                millisUntilNormalTimeout =
+                        mLastUserActivityTime + screenOffTimeout - mClock.uptimeMillis();
+                mDirty |= DIRTY_FACE_DOWN;
+                updatePowerStateLocked();
+            }
+        }
+        if (isFaceDown) {
+            mFaceDownDetector.setMillisSaved(millisUntilNormalTimeout);
+        }
+    }
+
     @Override
     public void onStart() {
         publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false,
@@ -1065,7 +1095,7 @@
             mBatteryStats = BatteryStatsService.getService();
             mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
                     mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
-                    mPolicy);
+                    mPolicy, mFaceDownDetector);
 
             mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
                     mInjector.createSuspendBlocker(
@@ -1099,6 +1129,7 @@
 
         mBatterySaverController.systemReady();
         mBatterySaverPolicy.systemReady();
+        mFaceDownDetector.systemReady(mContext);
 
         // Register for settings changes.
         resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -2283,9 +2314,11 @@
                     || getWakefulnessLocked() == WAKEFULNESS_DOZING) {
                 final long attentiveTimeout = getAttentiveTimeoutLocked();
                 final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
-                final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+                long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
                         attentiveTimeout);
                 final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+                screenOffTimeout =
+                        getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration);
                 final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
                 final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
 
@@ -2541,6 +2574,16 @@
                 (long)(screenOffTimeout * mMaximumScreenDimRatioConfig));
     }
 
+    private long getScreenOffTimeoutWithFaceDownLocked(
+            long screenOffTimeout, long screenDimDuration) {
+        // If face down, we decrease the timeout to equal the dim duration so that the
+        // device will go into a dim state.
+        if (mIsFaceDown) {
+            return Math.min(screenDimDuration, screenOffTimeout);
+        }
+        return screenOffTimeout;
+    }
+
     /**
      * Updates the wakefulness of the device.
      *
@@ -3859,6 +3902,8 @@
             pw.println("  mDisplayReady=" + mDisplayReady);
             pw.println("  mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker);
             pw.println("  mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
+            pw.println("  mLastFlipTime=" + mLastFlipTime);
+            pw.println("  mIsFaceDown=" + mIsFaceDown);
 
             pw.println();
             pw.println("Settings and Configuration:");
@@ -4003,6 +4048,8 @@
             mNotifier.dump(pw);
         }
 
+        mFaceDownDetector.dump(pw);
+
         mAmbientDisplaySuppressionController.dump(pw);
     }
 
@@ -5121,7 +5168,6 @@
 
         @Override // Binder call
         public int getPowerSaveModeTrigger() {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null);
             final long ident = Binder.clearCallingIdentity();
             try {
                 return Settings.Global.getInt(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
index 9a91848..f382d10 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.powerstats;
 
+import android.annotation.Nullable;
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.IPowerStats;
@@ -51,6 +52,7 @@
          *
          * @return List of information on each PowerEntity.
          */
+        @Nullable
         android.hardware.power.stats.PowerEntity[] getPowerEntityInfo();
 
         /**
@@ -70,6 +72,7 @@
          *
          * @return StateResidency since boot for each requested PowerEntity
          */
+        @Nullable
         android.hardware.power.stats.StateResidencyResult[] getStateResidency(int[] powerEntityIds);
 
         /**
@@ -81,6 +84,7 @@
          *
          * @return List of EnergyConsumers all available energy consumers.
          */
+        @Nullable
         android.hardware.power.stats.EnergyConsumer[] getEnergyConsumerInfo();
 
         /**
@@ -96,6 +100,7 @@
          * @return List of EnergyConsumerResult objects containing energy consumer results for all
          *         available energy consumers (power models).
          */
+        @Nullable
         android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed(
                 int[] energyConsumerIds);
 
@@ -105,6 +110,7 @@
          * @return List of Channel objects containing channel info for all available energy
          *         meters.
          */
+        @Nullable
         android.hardware.power.stats.Channel[] getEnergyMeterInfo();
 
         /**
@@ -120,6 +126,7 @@
          * @return List of EnergyMeasurement objects containing energy measurements for all
          *         available energy meters.
          */
+        @Nullable
         android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds);
 
         /**
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index bdabefb..ba778b3 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -87,6 +87,8 @@
             return StatsManager.PULL_SKIP;
         }
 
+        if (energyMeasurements == null) return StatsManager.PULL_SKIP;
+
         for (int i = 0; i < energyMeasurements.length; i++) {
             // Only report energy measurements that have been accumulated since boot
             final EnergyMeasurement energyMeasurement = energyMeasurements[i];
@@ -135,6 +137,8 @@
             return StatsManager.PULL_SKIP;
         }
 
+        if (results == null) return StatsManager.PULL_SKIP;
+
         for (int i = 0; i < results.length; i++) {
             final StateResidencyResult result = results[i];
             for (int j = 0; j < result.stateResidencyData.length; j++) {
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
index 6f7c016..0cd0458 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
@@ -65,6 +65,7 @@
     @GuardedBy("mLock")
     RemoteRotationResolverService mRemoteService;
 
+    private static String sTestingPackage;
     private ComponentName mComponentName;
 
     RotationResolverManagerPerUserService(@NonNull RotationResolverManagerService main,
@@ -136,19 +137,43 @@
     }
 
     /**
+     * Set the testing package name.
+     *
+     * @param packageName the name of the package that implements {@link RotationResolverService}
+     *                    and is used for testing only.
+     */
+    @VisibleForTesting
+    void setTestingPackage(String packageName) {
+        sTestingPackage = packageName;
+        mComponentName = resolveRotationResolverService(getContext());
+    }
+
+    /**
+     * get the currently bound component name.
+     */
+    @VisibleForTesting
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
      * Provides rotation resolver service component name at runtime, making sure it's provided
      * by the system.
      */
-    private static ComponentName resolveRotationResolverService(Context context) {
-        final String serviceConfigPackage = getServiceConfigPackage(context);
-
+    static ComponentName resolveRotationResolverService(Context context) {
         String resolvedPackage;
         int flags = PackageManager.MATCH_SYSTEM_ONLY;
-
-        if (!TextUtils.isEmpty(serviceConfigPackage)) {
-            resolvedPackage = serviceConfigPackage;
+        if (!TextUtils.isEmpty(sTestingPackage)) {
+            // Testing Package is set.
+            resolvedPackage = sTestingPackage;
+            flags = PackageManager.GET_META_DATA;
         } else {
-            return null;
+            final String serviceConfigPackage = getServiceConfigPackage(context);
+            if (!TextUtils.isEmpty(serviceConfigPackage)) {
+                resolvedPackage = serviceConfigPackage;
+            } else {
+                return null;
+            }
         }
 
         final Intent intent = new Intent(
@@ -158,14 +183,15 @@
                 flags, context.getUserId());
         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
             Slog.wtf(TAG, String.format("Service %s not found in package %s",
-                    RotationResolverService.SERVICE_INTERFACE, serviceConfigPackage
-            ));
+                    RotationResolverService.SERVICE_INTERFACE, resolvedPackage));
             return null;
         }
 
         final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
         final String permission = serviceInfo.permission;
         if (Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE.equals(permission)) {
+            Slog.i(TAG, String.format("Successfully bound the service from package: %s",
+                    resolvedPackage));
             return serviceInfo.getComponentName();
         }
         Slog.e(TAG, String.format(
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
index 54a9edb..e5088c0 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
@@ -16,12 +16,13 @@
 
 package com.android.server.rotationresolver;
 
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.os.CancellationSignal;
 import android.os.ShellCommand;
 import android.rotationresolver.RotationResolverInternal.RotationResolverCallbackInternal;
+import android.text.TextUtils;
 import android.view.Surface;
 
 import java.io.PrintWriter;
@@ -59,7 +60,7 @@
         }
     }
 
-    final TestableRotationCallbackInternal mTestableRotationCallbackInternal =
+    static final TestableRotationCallbackInternal sTestableRotationCallbackInternal =
             new TestableRotationCallbackInternal();
 
     @Override
@@ -73,20 +74,47 @@
                 return runResolveRotation();
             case "get-last-resolution":
                 return getLastResolution();
+            case "set-testing-package":
+                return setTestRotationResolverPackage(getNextArgRequired());
+            case "get-bound-package":
+                return getBoundPackageName();
+            case "clear-testing-package":
+                return resetTestRotationResolverPackage();
             default:
                 return handleDefaultCommands(cmd);
         }
     }
 
+    private int getBoundPackageName() {
+        final PrintWriter out = getOutPrintWriter();
+        final ComponentName componentName = mService.getComponentName();
+        out.println(componentName == null ? "" : componentName.getPackageName());
+        return 0;
+    }
+
+    private int setTestRotationResolverPackage(String testingPackage) {
+        if (!TextUtils.isEmpty((testingPackage))) {
+            mService.setTestingPackage(testingPackage);
+            sTestableRotationCallbackInternal.reset();
+        }
+        return 0;
+    }
+
+    private int resetTestRotationResolverPackage() {
+        mService.setTestingPackage("");
+        sTestableRotationCallbackInternal.reset();
+        return 0;
+    }
+
     private int runResolveRotation() {
-        mService.resolveRotationLocked(mTestableRotationCallbackInternal, Surface.ROTATION_0,
+        mService.resolveRotationLocked(sTestableRotationCallbackInternal, Surface.ROTATION_0,
                 Surface.ROTATION_0, "", 2000L, new CancellationSignal());
         return 0;
     }
 
     private int getLastResolution() {
         final PrintWriter out = getOutPrintWriter();
-        out.println(mTestableRotationCallbackInternal.getLastCallbackCode());
+        out.println(sTestableRotationCallbackInternal.getLastCallbackCode());
         return 0;
     }
 
@@ -99,5 +127,8 @@
         pw.println();
         pw.println("  resolve-rotation: request a rotation resolution.");
         pw.println("  get-last-resolution: show the last rotation resolution result.");
+        pw.println("  set-testing-package: Set the testing package that implements the service.");
+        pw.println("  get-bound-package: print the bound package that implements the service.");
+        pw.println("  clear-testing-package: reset the testing package.");
     }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 342dbfa..6aa7c8a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1471,12 +1471,18 @@
     }
 
     int pullCpuTimePerClusterFreqLocked(int atomTag, List<StatsEvent> pulledData) {
-        boolean success = KernelCpuTotalBpfMapReader.read((cluster, freq, timeMs) -> {
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, cluster, freq, timeMs));
-        });
-        if (!success) {
+        int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
+        long[] freqs = KernelCpuBpfTracking.getFreqs();
+        long[] timesMs = KernelCpuTotalBpfMapReader.read();
+        if (timesMs == null) {
             return StatsManager.PULL_SKIP;
         }
+        for (int freqIndex = 0; freqIndex < timesMs.length; ++freqIndex) {
+            int cluster = freqsClusters[freqIndex];
+            long freq = freqs[freqIndex];
+            long timeMs = timesMs[freqIndex];
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, cluster, freq, timeMs));
+        }
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -1503,48 +1509,42 @@
     }
 
     private void registerCpuCyclesPerUidCluster() {
-        int tagId = FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER;
-        PullAtomMetadata metadata = new PullAtomMetadata.Builder()
-                .setAdditiveFields(new int[] {3, 4, 5})
-                .build();
-        mStatsManager.setPullAtomCallback(
-                tagId,
-                metadata,
-                DIRECT_EXECUTOR,
-                mStatsCallbackImpl
-        );
+        // If eBPF tracking is not support, the procfs fallback is used if the kernel knows about
+        // CPU frequencies.
+        if (KernelCpuBpfTracking.isSupported() || KernelCpuBpfTracking.getClusters() > 0) {
+            int tagId = FrameworkStatsLog.CPU_CYCLES_PER_UID_CLUSTER;
+            PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                    .setAdditiveFields(new int[] {3, 4, 5})
+                    .build();
+            mStatsManager.setPullAtomCallback(
+                    tagId,
+                    metadata,
+                    DIRECT_EXECUTOR,
+                    mStatsCallbackImpl
+            );
+        }
     }
 
     int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
-        // TODO(b/179485697): Remove power profile dependency.
         PowerProfile powerProfile = new PowerProfile(mContext);
-        // Frequency index to frequency mapping.
-        long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
-        // Frequency index to cluster mapping.
-        int[] freqClusters = new int[freqs.length];
-        // Frequency index to power mapping.
-        double[] freqPowers = new double[freqs.length];
-        // Number of clusters.
-        int clusters;
-
-        // Initialize frequency mappings.
+        int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
+        int clusters = KernelCpuBpfTracking.getClusters();
+        long[] freqs = KernelCpuBpfTracking.getFreqs();
+        double[] freqsPowers = new double[freqs.length];
+        // Initialize frequency power mapping.
         {
-            int cluster = 0;
             int freqClusterIndex = 0;
-            long lastFreq = -1;
+            int lastCluster = -1;
             for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex, ++freqClusterIndex) {
-                long currFreq = freqs[freqIndex];
-                if (currFreq <= lastFreq) {
-                    cluster++;
+                int cluster = freqsClusters[freqIndex];
+                if (cluster != lastCluster) {
                     freqClusterIndex = 0;
                 }
-                freqClusters[freqIndex] = cluster;
-                freqPowers[freqIndex] =
-                        powerProfile.getAveragePowerForCpuCore(cluster, freqClusterIndex);
-                lastFreq = currFreq;
-            }
+                lastCluster = cluster;
 
-            clusters = cluster + 1;
+                freqsPowers[freqIndex] =
+                        powerProfile.getAveragePowerForCpuCore(cluster, freqClusterIndex);
+            }
         }
 
         // Aggregate 0: mcycles, 1: runtime ms, 2: power profile estimate for the same uids for
@@ -1570,12 +1570,12 @@
             }
 
             for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                int cluster = freqClusters[freqIndex];
+                int cluster = freqsClusters[freqIndex];
                 long timeMs = cpuFreqTimeMs[freqIndex];
                 values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES] += freqs[freqIndex] * timeMs;
                 values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 1] += timeMs;
                 values[cluster * CPU_CYCLES_PER_UID_CLUSTER_VALUES + 2] +=
-                        freqPowers[freqIndex] * timeMs;
+                        freqsPowers[freqIndex] * timeMs;
             }
         });
 
@@ -1665,34 +1665,6 @@
     }
 
     int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
-        // TODO(b/179485697): Remove power profile dependency.
-        PowerProfile powerProfile = new PowerProfile(mContext);
-        // Frequency index to frequency mapping.
-        long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
-        if (freqs == null) {
-            return StatsManager.PULL_SKIP;
-        }
-        // Frequency index to cluster mapping.
-        int[] freqClusters = new int[freqs.length];
-        // Number of clusters.
-        int clusters;
-
-        // Initialize frequency mappings.
-        {
-            int cluster = 0;
-            long lastFreq = -1;
-            for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex) {
-                long currFreq = freqs[freqIndex];
-                if (currFreq <= lastFreq) {
-                    cluster++;
-                }
-                freqClusters[freqIndex] = cluster;
-                lastFreq = currFreq;
-            }
-
-            clusters = cluster + 1;
-        }
-
         SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
                 .getSystemServiceCpuThreadTimes();
         if (times == null) {
@@ -1701,22 +1673,24 @@
 
         addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
                 FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER,
-                times.threadCpuTimesUs, clusters, freqs, freqClusters);
+                times.threadCpuTimesUs);
         addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
                 FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER,
-                times.binderThreadCpuTimesUs, clusters, freqs, freqClusters);
+                times.binderThreadCpuTimesUs);
 
         return StatsManager.PULL_SUCCESS;
     }
 
     private static void addCpuCyclesPerThreadGroupClusterAtoms(
-            int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs,
-            int clusters, long[] freqs, int[] freqClusters) {
+            int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs) {
+        int[] freqsClusters = KernelCpuBpfTracking.getFreqsClusters();
+        int clusters = KernelCpuBpfTracking.getClusters();
+        long[] freqs = KernelCpuBpfTracking.getFreqs();
         long[] aggregatedCycles = new long[clusters];
         long[] aggregatedTimesUs = new long[clusters];
         for (int i = 0; i < cpuTimesUs.length; ++i) {
-            aggregatedCycles[freqClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
-            aggregatedTimesUs[freqClusters[i]] += cpuTimesUs[i];
+            aggregatedCycles[freqsClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
+            aggregatedTimesUs[freqsClusters[i]] += cpuTimesUs[i];
         }
         for (int cluster = 0; cluster < clusters; ++cluster) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
diff --git a/services/core/java/com/android/server/utils/WatchedLongSparseArray.java b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
new file mode 100644
index 0000000..bf23de1
--- /dev/null
+++ b/services/core/java/com/android/server/utils/WatchedLongSparseArray.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.LongSparseArray;
+
+/**
+ * A watched variant of {@link LongSparseArray}.  If a {@link Watchable} is stored in the
+ * array, the array registers with the {@link Watchable}.  The array registers only once
+ * with each {@link Watchable} no matter how many times the {@link Watchable} is stored in
+ * the array.
+ * @param <E> The element type, stored in the array.
+ */
+public class WatchedLongSparseArray<E> extends WatchableImpl
+        implements Snappable {
+
+    // The storage
+    private final LongSparseArray<E> mStorage;
+
+    // If true, the array is watching its children
+    private volatile boolean mWatching = false;
+
+    // The local observer
+    private final Watcher mObserver = new Watcher() {
+            @Override
+            public void onChange(@Nullable Watchable o) {
+                WatchedLongSparseArray.this.dispatchChange(o);
+            }
+        };
+
+    /**
+     * A private convenience function that notifies registered listeners that an element
+     * has been added to or removed from the array.  The what parameter is the array itself.
+     */
+    private void onChanged() {
+        dispatchChange(this);
+    }
+
+    /**
+     * A convenience function.  Register the object if it is {@link Watchable} and if the
+     * array is currently watching.  Note that the watching flag must be true if this
+     * function is to succeed.
+     */
+    private void registerChild(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            ((Watchable) o).registerObserver(mObserver);
+        }
+    }
+
+    /**
+     * A convenience function.  Unregister the object if it is {@link Watchable} and if
+     * the array is currently watching.  Note that the watching flag must be true if this
+     * function is to succeed.
+     */
+    private void unregisterChild(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            ((Watchable) o).unregisterObserver(mObserver);
+        }
+    }
+
+    /**
+     * A convenience function.  Unregister the object if it is {@link Watchable}, if the array is
+     * currently watching, and if the storage does not contain the object.  Note that the watching
+     * flag must be true if this function is to succeed.  This must be called after an object has
+     * been removed from the storage.
+     */
+    private void unregisterChildIf(Object o) {
+        if (mWatching && o instanceof Watchable) {
+            if (mStorage.indexOfValue((E) o) == -1) {
+                ((Watchable) o).unregisterObserver(mObserver);
+            }
+        }
+    }
+
+    /**
+     * Register a {@link Watcher} with the array.  If this is the first Watcher than any
+     * array values that are {@link Watchable} are registered to the array itself.
+     */
+    @Override
+    public void registerObserver(@NonNull Watcher observer) {
+        super.registerObserver(observer);
+        if (registeredObserverCount() == 1) {
+            // The watching flag must be set true before any children are registered.
+            mWatching = true;
+            final int end = mStorage.size();
+            for (int i = 0; i < end; i++) {
+                registerChild(mStorage.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Unregister a {@link Watcher} from the array.  If this is the last Watcher than any
+     * array values that are {@link Watchable} are unregistered to the array itself.
+     */
+    @Override
+    public void unregisterObserver(@NonNull Watcher observer) {
+        super.unregisterObserver(observer);
+        if (registeredObserverCount() == 0) {
+            final int end = mStorage.size();
+            for (int i = 0; i < end; i++) {
+                unregisterChild(mStorage.valueAt(i));
+            }
+            // The watching flag must be true while children are unregistered.
+            mWatching = false;
+        }
+    }
+
+    /**
+     * Creates a new WatchedSparseArray containing no mappings.
+     */
+    public WatchedLongSparseArray() {
+        mStorage = new LongSparseArray();
+    }
+
+    /**
+     * Creates a new WatchedSparseArray containing no mappings that
+     * will not require any additional memory allocation to store the
+     * specified number of mappings.  If you supply an initial capacity of
+     * 0, the sparse array will be initialized with a light-weight
+     * representation not requiring any additional array allocations.
+     */
+    public WatchedLongSparseArray(int initialCapacity) {
+        mStorage = new LongSparseArray(initialCapacity);
+    }
+
+    /**
+     * Create a {@link WatchedLongSparseArray} from a {@link LongSparseArray}.
+     */
+    public WatchedLongSparseArray(@NonNull LongSparseArray<E> c) {
+        mStorage = c.clone();
+    }
+
+    /**
+     * The copy constructor does not copy the watcher data.
+     */
+    public WatchedLongSparseArray(@NonNull WatchedLongSparseArray<E> r) {
+        mStorage = r.mStorage.clone();
+    }
+
+    /**
+     * Make <this> a copy of src.  Any data in <this> is discarded.
+     */
+    public void copyFrom(@NonNull LongSparseArray<E> src) {
+        clear();
+        final int end = src.size();
+        for (int i = 0; i < end; i++) {
+            put(src.keyAt(i), src.valueAt(i));
+        }
+    }
+
+    /**
+     * Make dst a copy of <this>.  Any previous data in dst is discarded.
+     */
+    public void copyTo(@NonNull LongSparseArray<E> dst) {
+        dst.clear();
+        final int end = size();
+        for (int i = 0; i < end; i++) {
+            dst.put(keyAt(i), valueAt(i));
+        }
+    }
+
+    /**
+     * Return the underlying storage.  This breaks the wrapper but is necessary when
+     * passing the array to distant methods.
+     */
+    public LongSparseArray<E> untrackedStorage() {
+        return mStorage;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(long key) {
+        return mStorage.get(key);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    public E get(long key, E valueIfKeyNotFound) {
+        return mStorage.get(key, valueIfKeyNotFound);
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(long key) {
+        E old = mStorage.get(key, null);
+        mStorage.delete(key);
+        unregisterChildIf(old);
+        onChanged();
+
+    }
+
+    /**
+     * Alias for {@link #delete(long)}.
+     */
+    public void remove(long key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public void removeAt(int index) {
+        E old = mStorage.valueAt(index);
+        mStorage.removeAt(index);
+        unregisterChildIf(old);
+        onChanged();
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(long key, E value) {
+        E old = mStorage.get(key);
+        mStorage.put(key, value);
+        unregisterChildIf(old);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * Returns the number of key-value mappings that this LongSparseArray
+     * currently stores.
+     */
+    public int size() {
+        return mStorage.size();
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The keys corresponding to indices in ascending order are guaranteed to
+     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
+     * smallest key and <code>keyAt(size()-1)</code> will return the largest
+     * key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public long keyAt(int index) {
+        return mStorage.keyAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>The values corresponding to indices in ascending order are guaranteed
+     * to be associated with keys in ascending order, e.g.,
+     * <code>valueAt(0)</code> will return the value associated with the
+     * smallest key and <code>valueAt(size()-1)</code> will return the value
+     * associated with the largest key.</p>
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public E valueAt(int index) {
+        return mStorage.valueAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * LongSparseArray stores.
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>, an
+     * {@link ArrayIndexOutOfBoundsException} is thrown.</p>
+     */
+    public void setValueAt(int index, E value) {
+        E old = mStorage.valueAt(index);
+        mStorage.setValueAt(index, value);
+        unregisterChildIf(old);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(long key) {
+        return mStorage.indexOfKey(key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     */
+    public int indexOfValue(E value) {
+        return mStorage.indexOfValue(value);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that this method uses {@code equals} unlike {@code indexOfValue}.
+     * @hide
+     */
+    public int indexOfValueByValue(E value) {
+        return mStorage.indexOfValueByValue(value);
+    }
+
+    /**
+     * Removes all key-value mappings from this LongSparseArray.
+     */
+    public void clear() {
+        final int end = mStorage.size();
+        for (int i = 0; i < end; i++) {
+            unregisterChild(mStorage.valueAt(i));
+        }
+        mStorage.clear();
+        onChanged();
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(long key, E value) {
+        mStorage.append(key, value);
+        registerChild(value);
+        onChanged();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        return mStorage.toString();
+    }
+
+    /**
+     * Create a copy of the array.  If the element is a subclass of Snapper then the copy
+     * contains snapshots of the elements.  Otherwise the copy contains references to the
+     * elements.  The returned snapshot is immutable.
+     * @return A new array whose elements are the elements of <this>.
+     */
+    public WatchedLongSparseArray<E> snapshot() {
+        WatchedLongSparseArray<E> l = new WatchedLongSparseArray<>(size());
+        snapshot(l, this);
+        return l;
+    }
+
+    /**
+     * Make <this> a snapshot of the argument.  Note that <this> is immutable when the
+     * method returns.  <this> must be empty when the function is called.
+     * @param r The source array, which is copied into <this>
+     */
+    public void snapshot(@NonNull WatchedLongSparseArray<E> r) {
+        snapshot(this, r);
+    }
+
+    /**
+     * Make the destination a copy of the source.  If the element is a subclass of Snapper then the
+     * copy contains snapshots of the elements.  Otherwise the copy contains references to the
+     * elements.  The destination must be initially empty.  Upon return, the destination is
+     * immutable.
+     * @param dst The destination array.  It must be empty.
+     * @param src The source array.  It is not modified.
+     */
+    public static <E> void snapshot(@NonNull WatchedLongSparseArray<E> dst,
+            @NonNull WatchedLongSparseArray<E> src) {
+        if (dst.size() != 0) {
+            throw new IllegalArgumentException("snapshot destination is not empty");
+        }
+        final int end = src.size();
+        for (int i = 0; i < end; i++) {
+            final E val = Snapshots.maybeSnapshot(src.valueAt(i));
+            final long key = src.keyAt(i);
+            dst.put(key, val);
+        }
+        dst.seal();
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 904bbe8..151895f 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -235,7 +235,10 @@
     public void activityRelaunched(IBinder token) {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
-            mTaskSupervisor.activityRelaunchedLocked(token);
+            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+            if (r != null) {
+                r.finishRelaunching();
+            }
         }
         Binder.restoreCallingIdentity(origId);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8c3722d..5d3b9c1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -567,6 +567,7 @@
     boolean mVoiceInteraction;
 
     private int mPendingRelaunchCount;
+    long mRelaunchStartTime;
 
     // True if we are current in the process of removing this app token from the display
     private boolean mRemovingFromDisplay = false;
@@ -3357,7 +3358,11 @@
         return task.isDragResizing();
     }
 
+    @VisibleForTesting
     void startRelaunching() {
+        if (mPendingRelaunchCount == 0) {
+            mRelaunchStartTime = SystemClock.elapsedRealtime();
+        }
         if (shouldFreezeBounds()) {
             freezeBounds();
         }
@@ -3396,6 +3401,14 @@
             // Update keyguard flags upon finishing relaunch.
             checkKeyguardFlagsChanged();
         }
+
+        final Task rootTask = getRootTask();
+        if (rootTask != null && rootTask.shouldSleepOrShutDownActivities()) {
+            // Activity is always relaunched to either resumed or paused state. If it was
+            // relaunched while hidden (by keyguard or smth else), it should be stopped.
+            rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+                    false /* preserveWindows */);
+        }
     }
 
     void clearRelaunching() {
@@ -3404,6 +3417,7 @@
         }
         unfreezeBounds();
         mPendingRelaunchCount = 0;
+        mRelaunchStartTime = 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 75a7660..b803fc3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4015,8 +4015,7 @@
         deferWindowLayout();
         try {
             if (values != null) {
-                changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId,
-                        deferResume);
+                changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
             }
 
             if (!deferResume) {
@@ -4035,7 +4034,7 @@
 
     /** Update default (global) configuration and notify listeners about changes. */
     int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
-            boolean persistent, int userId, boolean deferResume) {
+            boolean persistent, int userId) {
 
         final DisplayContent defaultDisplay =
                 mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
@@ -4047,7 +4046,7 @@
             // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
             // performDisplayOverrideConfigUpdate in order to send the new display configuration
             // (even if there are no actual changes) to unfreeze the window.
-            defaultDisplay.performDisplayOverrideConfigUpdate(values, deferResume);
+            defaultDisplay.performDisplayOverrideConfigUpdate(values);
             return 0;
         }
 
@@ -4095,9 +4094,6 @@
 
         mTempConfig.seq = increaseConfigurationSeqLocked();
 
-        // Update stored global config and notify everyone about the change.
-        mRootWindowContainer.onConfigurationChanged(mTempConfig);
-
         Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
         mUsageStatsInternal.reportConfigurationChange(mTempConfig, mAmInternal.getCurrentUserId());
@@ -4116,13 +4112,10 @@
         // resources have that config before following boot code is executed.
         mSystemThread.applyConfigurationToResources(mTempConfig);
 
-        // We need another copy of global config because we're scheduling some calls instead of
-        // running them in place. We need to be sure that object we send will be handled unchanged.
-        final Configuration configCopy = new Configuration(mTempConfig);
         if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
             final Message msg = PooledLambda.obtainMessage(
                     ActivityTaskManagerService::sendPutConfigurationForUserMsg,
-                    this, userId, configCopy);
+                    this, userId, new Configuration(mTempConfig));
             mH.sendMessage(msg);
         }
 
@@ -4131,8 +4124,8 @@
             final int pid = pidMap.keyAt(i);
             final WindowProcessController app = pidMap.get(pid);
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
-                    + "config %s", app.mName, configCopy);
-            app.onConfigurationChanged(configCopy);
+                    + "config %s", app.mName, mTempConfig);
+            app.onConfigurationChanged(mTempConfig);
         }
 
         final Message msg = PooledLambda.obtainMessage(
@@ -4140,10 +4133,8 @@
                 mAmInternal, changes, initLocale);
         mH.sendMessage(msg);
 
-        // Override configuration of the default display duplicates global config, so we need to
-        // update it also. This will also notify WindowManager about changes.
-        defaultDisplay.performDisplayOverrideConfigUpdate(mRootWindowContainer.getConfiguration(),
-                deferResume);
+        // Update stored global config and notify everyone about the change.
+        mRootWindowContainer.onConfigurationChanged(mTempConfig);
 
         return changes;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1264d0c..bf2aae8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2204,19 +2204,6 @@
                 task.mTaskId, reason, topActivity.info.applicationInfo.packageName);
     }
 
-    void activityRelaunchedLocked(IBinder token) {
-        final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-        if (r != null) {
-            r.finishRelaunching();
-            if (r.getRootTask().shouldSleepOrShutDownActivities()) {
-                // Activity is always relaunched to either resumed or paused state. If it was
-                // relaunched while hidden (by keyguard or smth else), it should be stopped.
-                r.getRootTask().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
-                        false /* preserveWindows */);
-            }
-        }
-    }
-
     void logRootTaskState() {
         mActivityMetricsLogger.logWindowState();
     }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index efcaaa4..309b5ec 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -141,6 +141,10 @@
             mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                     mMergedOverrideConfiguration);
         }
+        dispatchConfigurationToChildren();
+    }
+
+    void dispatchConfigurationToChildren() {
         for (int i = getChildCount() - 1; i >= 0; --i) {
             final ConfigurationContainer child = getChildAt(i);
             child.onConfigurationChanged(mFullConfiguration);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 13e43a3..0143e70 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -673,10 +673,6 @@
     // Used in updating override configurations
     private final Configuration mTempConfig = new Configuration();
 
-    // Used in performing layout, to record the insets provided by other windows above the current
-    // window.
-    private InsetsState mTmpAboveInsetsState = new InsetsState();
-
     /**
      * Used to prevent recursions when calling
      * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
@@ -778,13 +774,6 @@
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
-        // Sets mAboveInsets for each window. Windows behind the window providing the insets can
-        // receive the insets.
-        if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) {
-            w.mAboveInsetsState.set(mTmpAboveInsetsState);
-            mWinInsetsChanged.add(w);
-        }
-
         // If this view is GONE, then skip it -- keep the current frame, and let the caller know
         // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
         // since that means "perform layout as normal, just don't display").
@@ -818,16 +807,8 @@
                     + " mContainingFrame=" + w.getContainingFrame()
                     + " mDisplayFrame=" + w.getDisplayFrame());
         }
-        provideInsetsByWindow(w);
     };
 
-    private void provideInsetsByWindow(WindowState w) {
-        for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) {
-            final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i);
-            mTmpAboveInsetsState.addSource(providedSource);
-        }
-    }
-
     private final Consumer<WindowState> mPerformLayoutAttached = w -> {
         if (w.mLayoutAttached) {
             if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -1572,12 +1553,12 @@
         }
         final int rotation = rotationForActivityInDifferentOrientation(r);
         if (rotation == ROTATION_UNDEFINED) {
-            // The display rotation won't be changed by current top activity. If there was fixed
-            // rotation activity, its rotated state should be cleared to cancel the adjustments.
-            if (hasTopFixedRotationLaunchingApp()
-                    // Avoid breaking recents animation.
-                    && !mFixedRotationLaunchingApp.getTask().isAnimatingByRecents()) {
-                clearFixedRotationLaunchingApp();
+            // The display rotation won't be changed by current top activity. The client side
+            // adjustments of previous rotated activity should be cleared earlier. Otherwise if
+            // the current top is in the same process, it may get the rotated state. The transform
+            // will be cleared later with transition callback to ensure smooth animation.
+            if (hasTopFixedRotationLaunchingApp()) {
+                mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
             }
             return false;
         }
@@ -2511,8 +2492,13 @@
 
     void onDisplayInfoChanged() {
         final DisplayInfo info = mDisplayInfo;
-        mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation),
-                calculateRoundedCornersForRotation(info.rotation));
+        if (mDisplayFrames.onDisplayInfoUpdated(info,
+                calculateDisplayCutoutForRotation(info.rotation),
+                calculateRoundedCornersForRotation(info.rotation))) {
+            // TODO(b/161810301): Set notifyInsetsChange to true while the server no longer performs
+            //                    layout.
+            mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChange */);
+        }
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -3767,8 +3753,11 @@
         // 2. Assign window layers based on the IME surface parent to make sure it is on top of the
         // app.
         assignWindowLayers(true /* setLayoutNeeded */);
-        // 3. Update the IME control target to apply any inset change and animation.
-        // 4. Reparent the IME container surface to either the input target app, or the IME window
+        // 3. The z-order of IME might have been changed. Update the above insets state.
+        mInsetsStateController.updateAboveInsetsState(
+                mInputMethodWindow, true /* notifyInsetsChange */);
+        // 4. Update the IME control target to apply any inset change and animation.
+        // 5. Reparent the IME container surface to either the input target app, or the IME window
         // parent.
         updateImeControlTarget();
     }
@@ -4341,14 +4330,6 @@
                     + " dh=" + mDisplayInfo.logicalHeight);
         }
 
-        // Used to indicate that we have processed the insets windows. This needs to be after
-        // beginLayoutLw to ensure the raw insets state display related info is initialized.
-        final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState();
-        mTmpAboveInsetsState = new InsetsState();
-        mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame());
-        mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout());
-        mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState);
-
         int seq = mLayoutSeq + 1;
         if (seq < 0) seq = 0;
         mLayoutSeq = seq;
@@ -5467,9 +5448,9 @@
                     // apply the correct override config.
                     changes = mAtmService.updateGlobalConfigurationLocked(values,
                             false /* initLocale */, false /* persistent */,
-                            UserHandle.USER_NULL /* userId */, deferResume);
+                            UserHandle.USER_NULL /* userId */);
                 } else {
-                    changes = performDisplayOverrideConfigUpdate(values, deferResume);
+                    changes = performDisplayOverrideConfigUpdate(values);
                 }
             }
 
@@ -5487,7 +5468,7 @@
         return kept;
     }
 
-    int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
+    int performDisplayOverrideConfigUpdate(Configuration values) {
         mTempConfig.setTo(getRequestedOverrideConfiguration());
         final int changes = mTempConfig.updateFrom(values);
         if (changes != 0) {
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index e4230a2..a37f3f2 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -21,6 +21,7 @@
 import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
@@ -70,25 +71,28 @@
      * @param info the updated {@link DisplayInfo}.
      * @param displayCutout the updated {@link DisplayCutout}.
      * @param roundedCorners the updated {@link RoundedCorners}.
+     * @return {@code true} if the insets state has been changed; {@code false} otherwise.
      */
-    public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout,
-            RoundedCorners roundedCorners) {
-        mDisplayWidth = info.logicalWidth;
-        mDisplayHeight = info.logicalHeight;
+    public boolean onDisplayInfoUpdated(DisplayInfo info, @NonNull WmDisplayCutout displayCutout,
+            @NonNull RoundedCorners roundedCorners) {
         mRotation = info.rotation;
-        final WmDisplayCutout wmDisplayCutout =
-                displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
 
         final InsetsState state = mInsetsState;
-        final Rect unrestricted = mUnrestricted;
         final Rect safe = mDisplayCutoutSafe;
-        final DisplayCutout cutout = wmDisplayCutout.getDisplayCutout();
+        final DisplayCutout cutout = displayCutout.getDisplayCutout();
+        if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
+                && state.getDisplayCutout().equals(cutout)
+                && state.getRoundedCorners().equals(roundedCorners)) {
+            return false;
+        }
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+        final Rect unrestricted = mUnrestricted;
         unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
         safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         state.setDisplayFrame(unrestricted);
         state.setDisplayCutout(cutout);
-        state.setRoundedCorners(roundedCorners != null ? roundedCorners
-                : RoundedCorners.NO_ROUNDED_CORNERS);
+        state.setRoundedCorners(roundedCorners);
         if (!cutout.isEmpty()) {
             if (cutout.getSafeInsetLeft() > 0) {
                 safe.left = unrestricted.left + cutout.getSafeInsetLeft();
@@ -116,6 +120,7 @@
             state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
             state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
         }
+        return true;
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 580d328..75176df 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -27,6 +27,9 @@
 import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -293,6 +296,76 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    /**
+     * Updates {@link WindowState#mAboveInsetsState} for all windows in the display while the
+     * z-order of a window is changed.
+     *
+     * @param win The window whose z-order has changed.
+     * @param notifyInsetsChange {@code true} if the clients should be notified about the change.
+     */
+    void updateAboveInsetsState(WindowState win, boolean notifyInsetsChange) {
+        if (win == null || win.getDisplayContent() != mDisplayContent) {
+            return;
+        }
+        final boolean[] aboveWin = { true };
+        final InsetsState aboveInsetsState = new InsetsState();
+        aboveInsetsState.set(mState,
+                displayCutout() | systemGestures() | mandatorySystemGestures());
+        final SparseArray<InsetsSource> winProvidedSources = win.mProvidedInsetsSources;
+        final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
+        mDisplayContent.forAllWindows(w -> {
+            if (aboveWin[0]) {
+                if (w == win) {
+                    aboveWin[0] = false;
+                    if (!win.mAboveInsetsState.equals(aboveInsetsState)) {
+                        win.mAboveInsetsState.set(aboveInsetsState);
+                        insetsChangedWindows.add(win);
+                    }
+                    return winProvidedSources.size() == 0;
+                } else {
+                    final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources;
+                    for (int i = providedSources.size() - 1; i >= 0; i--) {
+                        aboveInsetsState.addSource(providedSources.valueAt(i));
+                    }
+                    if (winProvidedSources.size() == 0) {
+                        return false;
+                    }
+                    boolean changed = false;
+                    for (int i = winProvidedSources.size() - 1; i >= 0; i--) {
+                        changed |= w.mAboveInsetsState.removeSource(winProvidedSources.keyAt(i));
+                    }
+                    if (changed) {
+                        insetsChangedWindows.add(w);
+                    }
+                }
+            } else {
+                for (int i = winProvidedSources.size() - 1; i >= 0; i--) {
+                    w.mAboveInsetsState.addSource(winProvidedSources.valueAt(i));
+                }
+                insetsChangedWindows.add(w);
+            }
+            return false;
+        }, true /* traverseTopToBottom */);
+        if (notifyInsetsChange) {
+            for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
+                mDispatchInsetsChanged.accept(insetsChangedWindows.get(i));
+            }
+        }
+    }
+
+    void onDisplayInfoUpdated(boolean notifyInsetsChange) {
+        final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
+        mDisplayContent.forAllWindows(w -> {
+            w.mAboveInsetsState.set(mState, displayCutout());
+            insetsChangedWindows.add(w);
+        }, true /* traverseTopToBottom */);
+        if (notifyInsetsChange) {
+            for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
+                mDispatchInsetsChanged.accept(insetsChangedWindows.get(i));
+            }
+        }
+    }
+
     void onInsetsModified(InsetsControlTarget caller) {
         boolean changed = false;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index bb5e8bf..3f9ea1f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -650,6 +650,20 @@
     }
 
     @Override
+    void dispatchConfigurationToChildren() {
+        final Configuration configuration = getConfiguration();
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent displayContent = getChildAt(i);
+            if (displayContent.isDefaultDisplay) {
+                // The global configuration is also the override configuration of default display.
+                displayContent.performDisplayOverrideConfigUpdate(configuration);
+            } else {
+                displayContent.onConfigurationChanged(configuration);
+            }
+        }
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         prepareFreezingTaskBounds();
         super.onConfigurationChanged(newParentConfig);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 39b749e..89d3040 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1881,6 +1881,10 @@
                 displayContent.sendNewConfiguration();
             }
 
+            // This window doesn't have a frame yet. Don't let this window cause the insets change.
+            displayContent.getInsetsStateController().updateAboveInsetsState(
+                    win, false /* notifyInsetsChanged */);
+
             getInsetsSourceControls(win, outActiveControls);
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a94b0aa..3dfdedf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -211,11 +211,11 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -533,6 +533,9 @@
      */
     private boolean mOrientationChanging;
 
+    /** The time when the window was last requested to redraw for orientation change. */
+    private long mOrientationChangeRedrawRequestTime;
+
     /**
      * Sometimes in addition to the mOrientationChanging
      * flag we report that the orientation is changing
@@ -648,12 +651,12 @@
     /**
      * The insets state of sources provided by windows above the current window.
      */
-    InsetsState mAboveInsetsState = new InsetsState();
+    final InsetsState mAboveInsetsState = new InsetsState();
 
     /**
      * The insets sources provided by this window.
      */
-    ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>();
+    final SparseArray<InsetsSource> mProvidedInsetsSources = new SparseArray<>();
 
     /**
      * Surface insets from the previous call to relayout(), used to track
@@ -1441,11 +1444,6 @@
             // redrawn; to do that, we need to go through the process of getting informed by the
             // application when it has finished drawing.
             if (getOrientationChanging() || dragResizingChanged) {
-                if (getOrientationChanging()) {
-                    Slog.v(TAG_WM, "Orientation start waiting for draw"
-                            + ", mDrawState=DRAW_PENDING in " + this
-                            + ", surfaceController " + winAnimator.mSurfaceController);
-                }
                 if (dragResizingChanged) {
                     ProtoLog.v(WM_DEBUG_RESIZE,
                             "Resize start waiting for draw, "
@@ -2204,7 +2202,7 @@
         }
     }
 
-  @Override
+    @Override
     void removeImmediately() {
         super.removeImmediately();
 
@@ -3651,7 +3649,8 @@
 
         ProtoLog.v(WM_DEBUG_RESIZE, "Reporting new frame to %s: %s", this,
                 mWindowFrames.mCompatFrame);
-        if (mWinAnimator.mDrawState == DRAW_PENDING) {
+        final boolean drawPending = mWinAnimator.mDrawState == DRAW_PENDING;
+        if (drawPending) {
             ProtoLog.i(WM_DEBUG_ORIENTATION, "Resizing %s WITH DRAW PENDING", this);
         }
 
@@ -3668,7 +3667,7 @@
         mWindowFrames.clearReportResizeHints();
 
         final MergedConfiguration mergedConfiguration = mLastReportedConfiguration;
-        final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING || useBLASTSync() || !mRedrawForSyncReported;
+        final boolean reportDraw = drawPending || useBLASTSync() || !mRedrawForSyncReported;
         final boolean forceRelayout = reportOrientation || isDragResizeChanged() || !mRedrawForSyncReported;
         final int displayId = getDisplayId();
         fillClientWindowFrames(mClientWindowFrames);
@@ -3679,6 +3678,11 @@
             mClient.resized(mClientWindowFrames, reportDraw, mergedConfiguration, forceRelayout,
                     getDisplayContent().getDisplayPolicy().areSystemBarsForcedShownLw(this),
                     displayId);
+            if (drawPending && reportOrientation && mOrientationChanging) {
+                mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
+                ProtoLog.v(WM_DEBUG_ORIENTATION,
+                        "Requested redraw for orientation change: %s", this);
+            }
 
             if (mWmService.mAccessibilityController != null) {
                 mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
@@ -5732,6 +5736,18 @@
     }
 
     boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
+        if (mOrientationChangeRedrawRequestTime > 0) {
+            final long duration =
+                    SystemClock.elapsedRealtime() - mOrientationChangeRedrawRequestTime;
+            Slog.i(TAG, "finishDrawing of orientation change: " + this + " " + duration + "ms");
+            mOrientationChangeRedrawRequestTime = 0;
+        } else if (mActivityRecord != null && mActivityRecord.mRelaunchStartTime != 0
+                && mActivityRecord.findMainWindow() == this) {
+            final long duration =
+                    SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
+            Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
+            mActivityRecord.mRelaunchStartTime = 0;
+        }
         if (!onSyncFinishedDrawing()) {
             return mWinAnimator.finishDrawingLocked(postDrawTransaction);
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 1c3fe02..e87ee91 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -549,7 +549,7 @@
     }
 
     /** Notifies application side to enable or disable the rotation adjustment of display info. */
-    private void notifyFixedRotationTransform(boolean enabled) {
+    void notifyFixedRotationTransform(boolean enabled) {
         FixedRotationAdjustments adjustments = null;
         // A token may contain windows of the same processes or different processes. The list is
         // used to avoid sending the same adjustments to a process multiple times.
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c1c79ee..29bce79 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -97,6 +97,7 @@
         "libbase",
         "libappfuse",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libcrypto",
         "liblog",
@@ -172,6 +173,7 @@
         "android.frameworks.schedulerservice@1.0",
         "android.frameworks.sensorservice@1.0",
         "android.frameworks.stats@1.0",
+        "android.frameworks.stats-V1-ndk_platform",
         "android.system.suspend.control-V1-cpp",
         "android.system.suspend.control.internal-cpp",
         "android.system.suspend@1.0",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 729fa71..a73f6c6 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -23,6 +23,7 @@
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 
+#include <android/binder_manager.h>
 #include <android/hidl/manager/1.2/IServiceManager.h>
 #include <binder/IServiceManager.h>
 #include <hidl/HidlTransportSupport.h>
@@ -31,6 +32,7 @@
 #include <schedulerservice/SchedulingPolicyService.h>
 #include <sensorservice/SensorService.h>
 #include <sensorservicehidl/SensorManager.h>
+#include <stats/StatsAidl.h>
 #include <stats/StatsHal.h>
 
 #include <bionic/malloc.h>
@@ -45,6 +47,31 @@
 using android::base::GetIntProperty;
 using namespace std::chrono_literals;
 
+namespace {
+
+static void startStatsAidlService() {
+    using aidl::android::frameworks::stats::IStats;
+    using aidl::android::frameworks::stats::StatsHal;
+
+    std::shared_ptr<StatsHal> statsService = ndk::SharedRefBase::make<StatsHal>();
+
+    const std::string instance = std::string() + IStats::descriptor + "/default";
+    const binder_exception_t err =
+            AServiceManager_addService(statsService->asBinder().get(), instance.c_str());
+    LOG_ALWAYS_FATAL_IF(err != EX_NONE, "Cannot register %s: %d", instance.c_str(), err);
+}
+
+static void startStatsHidlService() {
+    using android::frameworks::stats::V1_0::IStats;
+    using android::frameworks::stats::V1_0::implementation::StatsHal;
+
+    android::sp<IStats> statsHal = new StatsHal();
+    const android::status_t err = statsHal->registerAsService();
+    LOG_ALWAYS_FATAL_IF(err != android::OK, "Cannot register %s: %d", IStats::descriptor, err);
+}
+
+} // namespace
+
 namespace android {
 
 static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) {
@@ -54,7 +81,6 @@
         SensorService::publish(false /* allowIsolated */,
                                IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
     }
-
 }
 
 static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
@@ -62,8 +88,6 @@
     using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
     using ::android::frameworks::sensorservice::V1_0::ISensorManager;
     using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager;
-    using ::android::frameworks::stats::V1_0::IStats;
-    using ::android::frameworks::stats::V1_0::implementation::StatsHal;
     using ::android::hardware::configureRpcThreadpool;
     using ::android::hidl::manager::V1_0::IServiceManager;
 
@@ -89,9 +113,8 @@
         ALOGW("%s is deprecated. Skipping registration.", ISchedulingPolicyService::descriptor);
     }
 
-    sp<IStats> statsHal = new StatsHal();
-    err = statsHal->registerAsService();
-    LOG_ALWAYS_FATAL_IF(err != OK, "Cannot register %s: %d", IStats::descriptor, err);
+    startStatsAidlService();
+    startStatsHidlService();
 }
 
 static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
diff --git a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp b/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
index 3f54529..0e21aaf 100644
--- a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
+++ b/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
@@ -217,41 +217,30 @@
     jobjectArray stateResidencyResultArray = nullptr;
     Return<void> ret = gPowerStatsHalV1_0_ptr->getPowerEntityStateResidencyData(
             powerEntityIdVector, [&env, &stateResidencyResultArray](auto results, auto status) {
-                if (status != Status::SUCCESS) {
-                    ALOGE("Error getting power entity state residency data");
-                } else {
-                    stateResidencyResultArray =
-                            env->NewObjectArray(results.size(), class_SRR, nullptr);
-                    for (int i = 0; i < results.size(); i++) {
-                        jobjectArray stateResidencyArray =
-                                env->NewObjectArray(results[i].stateResidencyData.size(), class_SR,
-                                                    nullptr);
-                        for (int j = 0; j < results[i].stateResidencyData.size(); j++) {
-                            jobject stateResidency = env->NewObject(class_SR, method_SR_init);
-                            env->SetIntField(stateResidency, field_SR_id,
-                                             results[i].stateResidencyData[j].powerEntityStateId);
-                            env->SetLongField(stateResidency, field_SR_totalTimeInStateMs,
-                                              results[i].stateResidencyData[j].totalTimeInStateMs);
-                            env->SetLongField(stateResidency, field_SR_totalStateEntryCount,
-                                              results[i]
-                                                      .stateResidencyData[j]
-                                                      .totalStateEntryCount);
-                            env->SetLongField(stateResidency, field_SR_lastEntryTimestampMs,
-                                              results[i]
-                                                      .stateResidencyData[j]
-                                                      .lastEntryTimestampMs);
-                            env->SetObjectArrayElement(stateResidencyArray, j, stateResidency);
-                            env->DeleteLocalRef(stateResidency);
-                        }
-                        jobject stateResidencyResult = env->NewObject(class_SRR, method_SRR_init);
-                        env->SetIntField(stateResidencyResult, field_SRR_id,
-                                         results[i].powerEntityId);
-                        env->SetObjectField(stateResidencyResult, field_SRR_stateResidencyData,
-                                            stateResidencyArray);
-                        env->SetObjectArrayElement(stateResidencyResultArray, i,
-                                                   stateResidencyResult);
-                        env->DeleteLocalRef(stateResidencyResult);
+                stateResidencyResultArray = env->NewObjectArray(results.size(), class_SRR, nullptr);
+                for (int i = 0; i < results.size(); i++) {
+                    jobjectArray stateResidencyArray =
+                            env->NewObjectArray(results[i].stateResidencyData.size(), class_SR,
+                                                nullptr);
+                    for (int j = 0; j < results[i].stateResidencyData.size(); j++) {
+                        jobject stateResidency = env->NewObject(class_SR, method_SR_init);
+                        env->SetIntField(stateResidency, field_SR_id,
+                                         results[i].stateResidencyData[j].powerEntityStateId);
+                        env->SetLongField(stateResidency, field_SR_totalTimeInStateMs,
+                                          results[i].stateResidencyData[j].totalTimeInStateMs);
+                        env->SetLongField(stateResidency, field_SR_totalStateEntryCount,
+                                          results[i].stateResidencyData[j].totalStateEntryCount);
+                        env->SetLongField(stateResidency, field_SR_lastEntryTimestampMs,
+                                          results[i].stateResidencyData[j].lastEntryTimestampMs);
+                        env->SetObjectArrayElement(stateResidencyArray, j, stateResidency);
+                        env->DeleteLocalRef(stateResidency);
                     }
+                    jobject stateResidencyResult = env->NewObject(class_SRR, method_SRR_init);
+                    env->SetIntField(stateResidencyResult, field_SRR_id, results[i].powerEntityId);
+                    env->SetObjectField(stateResidencyResult, field_SRR_stateResidencyData,
+                                        stateResidencyArray);
+                    env->SetObjectArrayElement(stateResidencyResultArray, i, stateResidencyResult);
+                    env->DeleteLocalRef(stateResidencyResult);
                 }
             });
     if (!checkResult(ret, __func__)) {
@@ -320,30 +309,25 @@
             gPowerStatsHalV1_0_ptr
                     ->getEnergyData(channelIdVector,
                                     [&env, &energyMeasurementArray](auto energyData, auto status) {
-                                        if (status != Status::SUCCESS) {
-                                            ALOGW("Error getting energy data");
-                                        } else {
-                                            energyMeasurementArray =
-                                                    env->NewObjectArray(energyData.size(), class_EM,
-                                                                        nullptr);
-                                            for (int i = 0; i < energyData.size(); i++) {
-                                                jobject energyMeasurement =
-                                                        env->NewObject(class_EM, method_EM_init);
-                                                env->SetIntField(energyMeasurement, field_EM_id,
-                                                                 energyData[i].index);
-                                                env->SetLongField(energyMeasurement,
-                                                                  field_EM_timestampMs,
-                                                                  energyData[i].timestamp);
-                                                env->SetLongField(energyMeasurement,
-                                                                  field_EM_durationMs,
-                                                                  energyData[i].timestamp);
-                                                env->SetLongField(energyMeasurement,
-                                                                  field_EM_energyUWs,
-                                                                  energyData[i].energy);
-                                                env->SetObjectArrayElement(energyMeasurementArray,
-                                                                           i, energyMeasurement);
-                                                env->DeleteLocalRef(energyMeasurement);
-                                            }
+                                        energyMeasurementArray =
+                                                env->NewObjectArray(energyData.size(), class_EM,
+                                                                    nullptr);
+                                        for (int i = 0; i < energyData.size(); i++) {
+                                            jobject energyMeasurement =
+                                                    env->NewObject(class_EM, method_EM_init);
+                                            env->SetIntField(energyMeasurement, field_EM_id,
+                                                             energyData[i].index);
+                                            env->SetLongField(energyMeasurement,
+                                                              field_EM_timestampMs,
+                                                              energyData[i].timestamp);
+                                            env->SetLongField(energyMeasurement,
+                                                              field_EM_durationMs,
+                                                              energyData[i].timestamp);
+                                            env->SetLongField(energyMeasurement, field_EM_energyUWs,
+                                                              energyData[i].energy);
+                                            env->SetObjectArrayElement(energyMeasurementArray, i,
+                                                                       energyMeasurement);
+                                            env->DeleteLocalRef(energyMeasurement);
                                         }
                                     });
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 30a1e2a..7260732 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12344,7 +12344,6 @@
                 Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
                         DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
             }
-
             Preconditions.checkArgument(mSafetyChecker == checker,
                     "invalid checker: should be %s, was %s", mSafetyChecker, checker);
 
@@ -12352,7 +12351,6 @@
             extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
             extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
 
-            // TODO(b/178494483): add CTS test
             sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
                     extras);
             for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index 6ec6f91..776b444 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -72,11 +72,11 @@
         DevicePolicyManagerInternal dpmi = LocalServices
                 .getService(DevicePolicyManagerInternal.class);
 
-        Slog.i(TAG, "notifying " + reasonName + " is active");
-        dpmi.notifyUnsafeOperationStateChanged(this, reason, true);
+        Slog.i(TAG, "notifying " + reasonName + " is UNSAFE");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ false);
 
-        Slog.i(TAG, "notifying " + reasonName + " is inactive");
-        dpmi.notifyUnsafeOperationStateChanged(this, reason, false);
+        Slog.i(TAG, "notifying " + reasonName + " is SAFE");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, /* isSafe= */ true);
 
         Slog.i(TAG, "returning " + reasonName);
 
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
index 7e1e99f..5ffbd77 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -133,5 +133,8 @@
     ],
     static_libs: [
         "libgmock",
-    ]
+    ],
+    test_options: {
+        unit_test: true,
+    },
 }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 2fa927b..ce6e6ab 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1617,7 +1617,7 @@
     // Need a shared pointer: will be passing it into all unpacking jobs.
     std::shared_ptr<ZipArchive> zipFile(zipFileHandle, [](ZipArchiveHandle h) { CloseArchive(h); });
     void* cookie = nullptr;
-    const auto libFilePrefix = path::join(constants().libDir, abi) + "/";
+    const auto libFilePrefix = path::join(constants().libDir, abi) += "/";
     if (StartIteration(zipFile.get(), &cookie, libFilePrefix, constants().libSuffix)) {
         LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath;
         return false;
@@ -1627,6 +1627,17 @@
 
     auto openZipTs = Clock::now();
 
+    auto mapFiles = (mIncFs->features() & incfs::Features::v2);
+    incfs::FileId sourceId;
+    if (mapFiles) {
+        sourceId = mIncFs->getFileId(ifs->control, apkFullPath);
+        if (!incfs::isValidFileId(sourceId)) {
+            LOG(WARNING) << "Error getting IncFS file ID for apk path '" << apkFullPath
+                         << "', mapping disabled";
+            mapFiles = false;
+        }
+    }
+
     std::vector<Job> jobQueue;
     ZipEntry entry;
     std::string_view fileName;
@@ -1635,13 +1646,16 @@
             continue;
         }
 
+        const auto entryUncompressed = entry.method == kCompressStored;
+        const auto entryPageAligned = (entry.offset & (constants().blockSize - 1)) == 0;
+
         if (!extractNativeLibs) {
             // ensure the file is properly aligned and unpacked
-            if (entry.method != kCompressStored) {
+            if (!entryUncompressed) {
                 LOG(WARNING) << "Library " << fileName << " must be uncompressed to mmap it";
                 return false;
             }
-            if ((entry.offset & (constants().blockSize - 1)) != 0) {
+            if (!entryPageAligned) {
                 LOG(WARNING) << "Library " << fileName
                              << " must be page-aligned to mmap it, offset = 0x" << std::hex
                              << entry.offset;
@@ -1665,6 +1679,28 @@
             continue;
         }
 
+        if (mapFiles && entryUncompressed && entryPageAligned && entry.uncompressed_length > 0) {
+            incfs::NewMappedFileParams mappedFileParams = {
+                    .sourceId = sourceId,
+                    .sourceOffset = entry.offset,
+                    .size = entry.uncompressed_length,
+            };
+
+            if (auto res = mIncFs->makeMappedFile(ifs->control, targetLibPathAbsolute, 0755,
+                                                  mappedFileParams);
+                res == 0) {
+                if (perfLoggingEnabled()) {
+                    auto doneTs = Clock::now();
+                    LOG(INFO) << "incfs: Mapped " << libName << ": "
+                              << elapsedMcs(startFileTs, doneTs) << "mcs";
+                }
+                continue;
+            } else {
+                LOG(WARNING) << "Failed to map file for: '" << targetLibPath << "' errno: " << res
+                             << "; falling back to full extraction";
+            }
+        }
+
         // Create new lib file without signature info
         incfs::NewFileParams libFileParams = {
                 .size = entry.uncompressed_length,
@@ -1673,7 +1709,7 @@
                 .metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()},
         };
         incfs::FileId libFileId = idFromMetadata(targetLibPath);
-        if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0777, libFileId,
+        if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0755, libFileId,
                                         libFileParams)) {
             LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res;
             // If one lib file fails to be created, abort others as well
@@ -1900,25 +1936,33 @@
 }
 
 IncrementalService::LoadingProgress IncrementalService::getLoadingProgressFromPath(
-        const IncFsMount& ifs, std::string_view storagePath, bool stopOnFirstIncomplete) const {
-    ssize_t totalBlocks = 0, filledBlocks = 0;
-    const auto filePaths = mFs->listFilesRecursive(storagePath);
-    for (const auto& filePath : filePaths) {
+        const IncFsMount& ifs, std::string_view storagePath,
+        const bool stopOnFirstIncomplete) const {
+    ssize_t totalBlocks = 0, filledBlocks = 0, error = 0;
+    mFs->listFilesRecursive(storagePath, [&, this](auto filePath) {
         const auto [filledBlocksCount, totalBlocksCount] =
                 mIncFs->countFilledBlocks(ifs.control, filePath);
+        if (filledBlocksCount == -EOPNOTSUPP || filledBlocksCount == -ENOTSUP ||
+            filledBlocksCount == -ENOENT) {
+            // a kind of a file that's not really being loaded, e.g. a mapped range
+            // an older IncFS used to return ENOENT in this case, so handle it the same way
+            return true;
+        }
         if (filledBlocksCount < 0) {
             LOG(ERROR) << "getLoadingProgress failed to get filled blocks count for: " << filePath
                        << " errno: " << filledBlocksCount;
-            return {filledBlocksCount, filledBlocksCount};
+            error = filledBlocksCount;
+            return false;
         }
         totalBlocks += totalBlocksCount;
         filledBlocks += filledBlocksCount;
         if (stopOnFirstIncomplete && filledBlocks < totalBlocks) {
-            break;
+            return false;
         }
-    }
+        return true;
+    });
 
-    return {filledBlocks, totalBlocks};
+    return error ? LoadingProgress{error, error} : LoadingProgress{filledBlocks, totalBlocks};
 }
 
 bool IncrementalService::updateLoadingProgress(
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 659d650..d613289 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -134,10 +134,11 @@
     } mLooper;
 };
 
-class RealIncFs : public IncFsWrapper {
+class RealIncFs final : public IncFsWrapper {
 public:
     RealIncFs() = default;
     ~RealIncFs() final = default;
+    Features features() const final { return incfs::features(); }
     void listExistingMounts(const ExistingMountCallback& cb) const final {
         for (auto mount : incfs::defaultMountRegistry().copyMounts()) {
             auto binds = mount.binds(); // span() doesn't like rvalue containers, needs to save it.
@@ -153,6 +154,10 @@
                        incfs::NewFileParams params) const final {
         return incfs::makeFile(control, path, mode, id, params);
     }
+    ErrorCode makeMappedFile(const Control& control, std::string_view path, int mode,
+                             incfs::NewMappedFileParams params) const final {
+        return incfs::makeMappedFile(control, path, mode, params);
+    }
     ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final {
         return incfs::makeDir(control, path, mode);
     }
@@ -282,11 +287,10 @@
             auto it = mJobs.begin();
             // Always acquire begin(). We can't use it after unlock as mTimedJobs can change.
             for (; it != mJobs.end() && it->when <= now; it = mJobs.begin()) {
-                auto job = std::move(it->what);
-                mJobs.erase(it);
+                auto jobNode = mJobs.extract(it);
 
                 lock.unlock();
-                job();
+                jobNode.value().what();
                 lock.lock();
             }
             nextJobTs = it != mJobs.end() ? it->when : kInfinityTs;
@@ -308,20 +312,20 @@
     std::thread mThread;
 };
 
-class RealFsWrapper : public FsWrapper {
+class RealFsWrapper final : public FsWrapper {
 public:
     RealFsWrapper() = default;
     ~RealFsWrapper() = default;
 
-    std::vector<std::string> listFilesRecursive(std::string_view directoryPath) const final {
-        std::vector<std::string> files;
+    void listFilesRecursive(std::string_view directoryPath, FileCallback onFile) const final {
         for (const auto& entry : std::filesystem::recursive_directory_iterator(directoryPath)) {
             if (!entry.is_regular_file()) {
                 continue;
             }
-            files.push_back(entry.path().c_str());
+            if (!onFile(entry.path().native())) {
+                break;
+            }
         }
-        return files;
     }
 };
 
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index d60035a..245bb31 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <android-base/function_ref.h>
 #include <android-base/unique_fd.h>
 #include <android/content/pm/DataLoaderParamsParcel.h>
 #include <android/content/pm/FileSystemControlParcel.h>
@@ -77,18 +78,22 @@
     using ErrorCode = incfs::ErrorCode;
     using UniqueFd = incfs::UniqueFd;
     using WaitResult = incfs::WaitResult;
+    using Features = incfs::Features;
 
-    using ExistingMountCallback =
-            std::function<void(std::string_view root, std::string_view backingDir,
-                               std::span<std::pair<std::string_view, std::string_view>> binds)>;
+    using ExistingMountCallback = android::base::function_ref<
+            void(std::string_view root, std::string_view backingDir,
+                 std::span<std::pair<std::string_view, std::string_view>> binds)>;
 
     virtual ~IncFsWrapper() = default;
+    virtual Features features() const = 0;
     virtual void listExistingMounts(const ExistingMountCallback& cb) const = 0;
     virtual Control openMount(std::string_view path) const = 0;
     virtual Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs,
                                   IncFsFd blocksWritten) const = 0;
     virtual ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
                                incfs::NewFileParams params) const = 0;
+    virtual ErrorCode makeMappedFile(const Control& control, std::string_view path, int mode,
+                                     incfs::NewMappedFileParams params) const = 0;
     virtual ErrorCode makeDir(const Control& control, std::string_view path, int mode) const = 0;
     virtual ErrorCode makeDirs(const Control& control, std::string_view path, int mode) const = 0;
     virtual incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const = 0;
@@ -148,7 +153,9 @@
 class FsWrapper {
 public:
     virtual ~FsWrapper() = default;
-    virtual std::vector<std::string> listFilesRecursive(std::string_view directoryPath) const = 0;
+
+    using FileCallback = android::base::function_ref<bool(std::string_view)>;
+    virtual void listFilesRecursive(std::string_view directoryPath, FileCallback onFile) const = 0;
 };
 
 class ServiceManagerWrapper {
diff --git a/services/incremental/TEST_MAPPING b/services/incremental/TEST_MAPPING
new file mode 100644
index 0000000..d935256
--- /dev/null
+++ b/services/incremental/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+        },
+        {
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
+        },
+        {
+          "include-filter": "android.content.pm.cts.ChecksumsTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageManagerStatsHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.android.cts.packagemanager.stats.host.PackageInstallerV2StatsTests"
+        }
+      ]
+    },
+    {
+      "name": "CtsIncrementalInstallHostTestCases"
+    },
+    {
+      "name": "CtsInstalledLoadingProgressHostTests"
+    }
+  ]
+}
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index ab491ef..b00a84f 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -322,6 +322,7 @@
 
 class MockIncFs : public IncFsWrapper {
 public:
+    MOCK_CONST_METHOD0(features, Features());
     MOCK_CONST_METHOD1(listExistingMounts, void(const ExistingMountCallback& cb));
     MOCK_CONST_METHOD1(openMount, Control(std::string_view path));
     MOCK_CONST_METHOD4(createControl,
@@ -330,6 +331,9 @@
     MOCK_CONST_METHOD5(makeFile,
                        ErrorCode(const Control& control, std::string_view path, int mode, FileId id,
                                  NewFileParams params));
+    MOCK_CONST_METHOD4(makeMappedFile,
+                       ErrorCode(const Control& control, std::string_view path, int mode,
+                                 NewMappedFileParams params));
     MOCK_CONST_METHOD3(makeDir, ErrorCode(const Control& control, std::string_view path, int mode));
     MOCK_CONST_METHOD3(makeDirs,
                        ErrorCode(const Control& control, std::string_view path, int mode));
@@ -539,16 +543,16 @@
 
 class MockFsWrapper : public FsWrapper {
 public:
-    MOCK_CONST_METHOD1(listFilesRecursive, std::vector<std::string>(std::string_view));
-    void hasNoFile() {
-        ON_CALL(*this, listFilesRecursive(_)).WillByDefault(Return(std::vector<std::string>()));
-    }
+    MOCK_CONST_METHOD2(listFilesRecursive, void(std::string_view, FileCallback));
+    void hasNoFile() { ON_CALL(*this, listFilesRecursive(_, _)).WillByDefault(Return()); }
     void hasFiles() {
-        ON_CALL(*this, listFilesRecursive(_))
+        ON_CALL(*this, listFilesRecursive(_, _))
                 .WillByDefault(Invoke(this, &MockFsWrapper::fakeFiles));
     }
-    std::vector<std::string> fakeFiles(std::string_view directoryPath) {
-        return {"base.apk", "split.apk", "lib/a.so"};
+    void fakeFiles(std::string_view directoryPath, FileCallback onFile) {
+        for (auto file : {"base.apk", "split.apk", "lib/a.so"}) {
+            if (!onFile(file)) break;
+        }
     }
 };
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 27825a4..a382e85 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -64,6 +64,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalAnswers.answer;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.doAnswer;
@@ -73,6 +74,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.content.ComponentName;
@@ -178,11 +181,20 @@
         setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
         setFieldValue(ActivityManagerService.class, sService, "mProcLock",
                 new ActivityManagerProcLock());
+        setFieldValue(ActivityManagerService.class, sService, "mServices",
+                spy(new ActiveServices(sService)));
+        setFieldValue(ActivityManagerService.class, sService, "mInternal",
+                mock(ActivityManagerService.LocalService.class));
+        setFieldValue(ActivityManagerService.class, sService, "mBatteryStatsService",
+                mock(BatteryStatsService.class));
+        doReturn(mock(AppOpsManager.class)).when(sService).getAppOpsManager();
+        doCallRealMethod().when(sService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
+        doCallRealMethod().when(sService).updateOomAdjPendingTargetsLocked(any(String.class));
         setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
         doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
                 .enqueueProcessChangeItemLocked(anyInt(), anyInt());
         sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
-                mock(ActiveUids.class));
+                new ActiveUids(sService, false));
         sService.mOomAdjuster.mAdjSeq = 10000;
         sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
     }
@@ -1315,6 +1327,115 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_UidIdle_StopService() {
+        final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP3_UID,
+                MOCKAPP4_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        final ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
+                MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
+        final UidRecord app1UidRecord = new UidRecord(MOCKAPP_UID, sService);
+        final UidRecord app2UidRecord = new UidRecord(MOCKAPP2_UID, sService);
+        final UidRecord app3UidRecord = new UidRecord(MOCKAPP5_UID, sService);
+        final UidRecord clientUidRecord = new UidRecord(MOCKAPP3_UID, sService);
+        app1.setUidRecord(app1UidRecord);
+        app2.setUidRecord(app2UidRecord);
+        app3.setUidRecord(app3UidRecord);
+        client1.setUidRecord(clientUidRecord);
+        client2.setUidRecord(clientUidRecord);
+
+        client1.mServices.setHasForegroundServices(true, 0);
+        client2.mState.setForcingToImportant(new Object());
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+        lru.clear();
+        lru.add(app1);
+        lru.add(app2);
+        lru.add(app3);
+        lru.add(client1);
+        lru.add(client2);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+        final ComponentName cn1 = ComponentName.unflattenFromString(
+                MOCKAPP_PACKAGENAME + "/.TestService");
+        final ServiceRecord s1 = bindService(app1, client1, null, 0, mock(IBinder.class));
+        setFieldValue(ServiceRecord.class, s1, "name", cn1);
+        s1.startRequested = true;
+
+        final ComponentName cn2 = ComponentName.unflattenFromString(
+                MOCKAPP2_PACKAGENAME + "/.TestService");
+        final ServiceRecord s2 = bindService(app2, client2, null, 0, mock(IBinder.class));
+        setFieldValue(ServiceRecord.class, s2, "name", cn2);
+        s2.startRequested = true;
+
+        final ComponentName cn3 = ComponentName.unflattenFromString(
+                MOCKAPP5_PACKAGENAME + "/.TestService");
+        final ServiceRecord s3 = bindService(app3, client1, null, 0, mock(IBinder.class));
+        setFieldValue(ServiceRecord.class, s3, "name", cn3);
+        s3.startRequested = true;
+
+        final ComponentName cn4 = ComponentName.unflattenFromString(
+                MOCKAPP3_PACKAGENAME + "/.TestService");
+        final ServiceRecord c2s = makeServiceRecord(client2);
+        setFieldValue(ServiceRecord.class, c2s, "name", cn4);
+        c2s.startRequested = true;
+
+        try {
+            sService.mOomAdjuster.mActiveUids.put(MOCKAPP_UID, app1UidRecord);
+            sService.mOomAdjuster.mActiveUids.put(MOCKAPP2_UID, app2UidRecord);
+            sService.mOomAdjuster.mActiveUids.put(MOCKAPP5_UID, app3UidRecord);
+            sService.mOomAdjuster.mActiveUids.put(MOCKAPP3_UID, clientUidRecord);
+
+            setServiceMap(s1, MOCKAPP_UID, cn1);
+            setServiceMap(s2, MOCKAPP2_UID, cn2);
+            setServiceMap(s3, MOCKAPP5_UID, cn3);
+            setServiceMap(c2s, MOCKAPP3_UID, cn4);
+            app2UidRecord.setIdle(false);
+            sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                    SCHED_GROUP_DEFAULT);
+            assertProcStates(app3, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                    SCHED_GROUP_DEFAULT);
+            assertProcStates(client1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+                    SCHED_GROUP_DEFAULT);
+            assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app2.mState.getSetProcState());
+            assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, client2.mState.getSetProcState());
+
+            client1.mServices.setHasForegroundServices(false, 0);
+            client2.mState.setForcingToImportant(null);
+            app1UidRecord.reset();
+            app2UidRecord.reset();
+            app3UidRecord.reset();
+            clientUidRecord.reset();
+            app1UidRecord.setIdle(true);
+            app2UidRecord.setIdle(true);
+            app3UidRecord.setIdle(true);
+            clientUidRecord.setIdle(true);
+            doReturn(ActivityManager.APP_START_MODE_DELAYED).when(sService)
+                    .getAppStartModeLOSP(anyInt(), any(String.class), anyInt(),
+                    anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
+            doNothing().when(sService.mServices)
+                    .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
+            sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+            assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
+            assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
+            assertEquals(PROCESS_STATE_SERVICE, client2.mState.getSetProcState());
+        } finally {
+            doCallRealMethod().when(sService)
+                    .getAppStartModeLOSP(anyInt(), any(String.class), anyInt(),
+                    anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
+            sService.mServices.mServiceMap.clear();
+            sService.mOomAdjuster.mActiveUids.clear();
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_Unbound() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
@@ -1815,15 +1936,42 @@
         return app;
     }
 
+    private ServiceRecord makeServiceRecord(ProcessRecord app) {
+        final ServiceRecord record = mock(ServiceRecord.class);
+        record.app = app;
+        setFieldValue(ServiceRecord.class, record, "connections",
+                new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
+        doCallRealMethod().when(record).getConnections();
+        setFieldValue(ServiceRecord.class, record, "packageName", app.info.packageName);
+        app.mServices.startService(record);
+        record.appInfo = app.info;
+        setFieldValue(ServiceRecord.class, record, "bindings", new ArrayMap<>());
+        setFieldValue(ServiceRecord.class, record, "pendingStarts", new ArrayList<>());
+        return record;
+    }
+
+    private void setServiceMap(ServiceRecord s, int uid, ComponentName cn) {
+        ActiveServices.ServiceMap serviceMap = sService.mServices.mServiceMap.get(
+                UserHandle.getUserId(uid));
+        if (serviceMap == null) {
+            serviceMap = mock(ActiveServices.ServiceMap.class);
+            setFieldValue(ActiveServices.ServiceMap.class, serviceMap, "mServicesByInstanceName",
+                    new ArrayMap<>());
+            setFieldValue(ActiveServices.ServiceMap.class, serviceMap, "mActiveForegroundApps",
+                    new ArrayMap<>());
+            setFieldValue(ActiveServices.ServiceMap.class, serviceMap, "mServicesByIntent",
+                    new ArrayMap<>());
+            setFieldValue(ActiveServices.ServiceMap.class, serviceMap, "mDelayedStartList",
+                    new ArrayList<>());
+            sService.mServices.mServiceMap.put(UserHandle.getUserId(uid), serviceMap);
+        }
+        serviceMap.mServicesByInstanceName.put(cn, s);
+    }
+
     private ServiceRecord bindService(ProcessRecord service, ProcessRecord client,
             ServiceRecord record, int bindFlags, IBinder binder) {
         if (record == null) {
-            record = mock(ServiceRecord.class);
-            record.app = service;
-            setFieldValue(ServiceRecord.class, record, "connections",
-                    new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
-            service.mServices.startService(record);
-            doCallRealMethod().when(record).getConnections();
+            record = makeServiceRecord(service);
         }
         AppBindRecord binding = new AppBindRecord(record, null, client);
         ConnectionRecord cr = spy(new ConnectionRecord(binding,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index ca53492..7f8784d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -223,7 +223,7 @@
         for (int i = 0; i < wrappedModes.length; i++) {
             modes[i] = wrappedModes[i].mode;
         }
-        display.modes = modes;
+        display.dynamicInfo.supportedDisplayModes = modes;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
@@ -252,53 +252,53 @@
 
         testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 60f, 0), new float[]{24f, 50f}),
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 50f, 0), new float[]{24f, 60f}),
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 24f, 0), new float[]{50f, 60f}),
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 60f, 0), new float[]{24f, 50f}),
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 50f, 0), new float[]{24f, 60f}),
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 24f, 0), new float[]{50f, 60f}),
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
         });
 
         testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 60f, 0), new float[]{50f}),
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 50f, 0), new float[]{60f}),
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 24f, 1), new float[0]),
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 60f, 2), new float[0]),
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 50f, 3), new float[]{24f}),
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 24f, 3), new float[]{50f}),
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
         });
 
         testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 60f, 0), new float[0]),
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 50f, 1), new float[0]),
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(1920, 1080, 24f, 2), new float[0]),
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 60f, 3), new float[0]),
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 50f, 4), new float[0]),
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
                 new DisplayModeWrapper(
-                        createFakeDisplayMode(3840, 2160, 24f, 5), new float[0]),
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
         });
     }
 
     @Test
     public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(1920, 1080, 60f);
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
         FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
@@ -325,18 +325,14 @@
                 displayMode.refreshRate)).isTrue();
 
         // Change the display
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3840, 2160,
-                60f);
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
         modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
-        display.modes = modes;
-        display.activeMode = 1;
+        display.dynamicInfo.supportedDisplayModes = modes;
+        display.dynamicInfo.activeDisplayModeId = 1;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
 
-        assertThat(SurfaceControl.getActiveDisplayMode(display.token)).isEqualTo(1);
-        assertThat(SurfaceControl.getDisplayModes(display.token).length).isEqualTo(2);
-
         assertThat(mListener.addedDisplays.size()).isEqualTo(1);
         assertThat(mListener.changedDisplays.size()).isEqualTo(1);
 
@@ -360,8 +356,8 @@
     @Test
     public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
         SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(1920, 1080, 60f),
-                createFakeDisplayMode(1920, 1080, 50f)
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
         };
         FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
         setUpDisplay(display);
@@ -379,13 +375,11 @@
         assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
 
         // Change the display
-        display.activeMode = 1;
+        display.dynamicInfo.activeDisplayModeId = 1;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
 
-        assertThat(SurfaceControl.getActiveDisplayMode(display.token)).isEqualTo(1);
-
         assertThat(mListener.addedDisplays.size()).isEqualTo(1);
         assertThat(mListener.changedDisplays.size()).isEqualTo(1);
 
@@ -402,7 +396,7 @@
         FakeDisplay display = new FakeDisplay(PORT_A);
         Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
                 1000, 1000, 0);
-        display.hdrCapabilities = initialHdrCapabilities;
+        display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -419,7 +413,7 @@
         // Change the display
         Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
                 new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
-        display.hdrCapabilities = changedHdrCapabilities;
+        display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
@@ -438,7 +432,7 @@
     public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
         final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
-        display.colorModes = initialColorModes;
+        display.dynamicInfo.supportedColorModes = initialColorModes;
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -455,7 +449,7 @@
 
         // Change the display
         final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
-        display.colorModes = changedColorModes;
+        display.dynamicInfo.supportedColorModes = changedColorModes;
         setUpDisplay(display);
         mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
@@ -474,8 +468,8 @@
     @Test
     public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
         SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(1920, 1080, 60f),
-                createFakeDisplayMode(1920, 1080, 50f)
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
         };
         final int activeMode = 0;
         FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
@@ -487,11 +481,11 @@
         waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
 
         // Change the display
-        display.modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(1920, 1080, 60f)
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(2, 1920, 1080, 60f)
         };
-        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't
-        // trigger ArrayOutOfBoundsException.
+        display.dynamicInfo.activeDisplayModeId = 2;
+        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
         display.desiredDisplayModeSpecs.defaultMode = 1;
 
         setUpDisplay(display);
@@ -588,12 +582,15 @@
     private static class FakeDisplay {
         public final DisplayAddress.Physical address;
         public final IBinder token = new Binder();
-        public final SurfaceControl.DisplayInfo info;
-        public SurfaceControl.DisplayMode[] modes;
-        public int activeMode;
-        public int[] colorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
-        public Display.HdrCapabilities hdrCapabilities = new Display.HdrCapabilities(new int[0],
-                1000, 1000, 0);
+        public final SurfaceControl.StaticDisplayInfo info;
+        public SurfaceControl.DynamicDisplayInfo dynamicInfo =
+                new SurfaceControl.DynamicDisplayInfo();
+        {
+            dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
+            dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
+                    1000, 1000, 0);
+        }
+
         public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
                 new SurfaceControl.DesiredDisplayModeSpecs(/* defaultMode */ 0,
                     /* allowGroupSwitching */ false,
@@ -603,19 +600,19 @@
                     /* appRefreshRateMax */60.f);
 
         private FakeDisplay(int port) {
-            this.address = createDisplayAddress(port);
-            this.info = createFakeDisplayInfo();
-            this.modes = new SurfaceControl.DisplayMode[]{
-                    createFakeDisplayMode(800, 600, 60f)
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                    createFakeDisplayMode(0, 800, 600, 60f)
             };
-            this.activeMode = 0;
+            dynamicInfo.activeDisplayModeId = 0;
         }
 
         private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
-            this.address = createDisplayAddress(port);
-            this.info = createFakeDisplayInfo();
-            this.modes = modes;
-            this.activeMode = activeMode;
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = modes;
+            dynamicInfo.activeDisplayModeId = activeMode;
         }
     }
 
@@ -623,15 +620,9 @@
         mAddresses.add(display.address);
         doReturn(display.token).when(() ->
                 SurfaceControl.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()));
-        doReturn(display.info).when(() -> SurfaceControl.getDisplayInfo(display.token));
-        doReturn(display.modes).when(
-                () -> SurfaceControl.getDisplayModes(display.token));
-        doReturn(display.activeMode).when(() -> SurfaceControl.getActiveDisplayMode(display.token));
-        doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token));
-        doReturn(display.colorModes).when(
-                () -> SurfaceControl.getDisplayColorModes(display.token));
-        doReturn(display.hdrCapabilities).when(
-                () -> SurfaceControl.getHdrCapabilities(display.token));
+        doReturn(display.info).when(() -> SurfaceControl.getStaticDisplayInfo(display.token));
+        doReturn(display.dynamicInfo).when(
+                () -> SurfaceControl.getDynamicDisplayInfo(display.token));
         doReturn(display.desiredDisplayModeSpecs)
                 .when(() -> SurfaceControl.getDesiredDisplayModeSpecs(display.token));
     }
@@ -650,20 +641,21 @@
         return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
     }
 
-    private static SurfaceControl.DisplayInfo createFakeDisplayInfo() {
-        final SurfaceControl.DisplayInfo info = new SurfaceControl.DisplayInfo();
+    private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
+        final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
         info.density = 100;
         return info;
     }
 
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int width, int height,
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
             float refreshRate) {
-        return createFakeDisplayMode(width, height, refreshRate, 0);
+        return createFakeDisplayMode(id, width, height, refreshRate, 0);
     }
 
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int width, int height,
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
             float refreshRate, int group) {
         final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
+        mode.id = id;
         mode.width = width;
         mode.height = height;
         mode.refreshRate = refreshRate;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 6d40034..91b3cb7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -101,7 +102,7 @@
                 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
         JobSchedulerService.sUptimeMillisClock =
                 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC);
-        JobSchedulerService.sElapsedRealtimeClock =
+        sElapsedRealtimeClock =
                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
     }
 
@@ -204,7 +205,7 @@
 
     @Test
     public void testFraction() throws Exception {
-        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        final long now = sElapsedRealtimeClock.millis();
 
         assertEquals(1, createJobStatus(0, Long.MAX_VALUE).getFractionRunTime(), DELTA);
 
@@ -261,15 +262,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setChargingConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
-        job.setChargingConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setChargingConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
-        job.setChargingConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
     }
 
@@ -282,15 +283,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setIdleConstraintSatisfied(false);
+        job.setIdleConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
-        job.setIdleConstraintSatisfied(true);
+        job.setIdleConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setIdleConstraintSatisfied(false);
+        job.setIdleConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
-        job.setIdleConstraintSatisfied(true);
+        job.setIdleConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
     }
 
@@ -303,15 +304,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setBatteryNotLowConstraintSatisfied(false);
+        job.setBatteryNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
-        job.setBatteryNotLowConstraintSatisfied(true);
+        job.setBatteryNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setBatteryNotLowConstraintSatisfied(false);
+        job.setBatteryNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
-        job.setBatteryNotLowConstraintSatisfied(true);
+        job.setBatteryNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
     }
 
@@ -324,15 +325,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setStorageNotLowConstraintSatisfied(false);
+        job.setStorageNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
-        job.setStorageNotLowConstraintSatisfied(true);
+        job.setStorageNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setStorageNotLowConstraintSatisfied(false);
+        job.setStorageNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
-        job.setStorageNotLowConstraintSatisfied(true);
+        job.setStorageNotLowConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
     }
 
@@ -345,15 +346,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setTimingDelayConstraintSatisfied(false);
+        job.setTimingDelayConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
-        job.setTimingDelayConstraintSatisfied(true);
+        job.setTimingDelayConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setTimingDelayConstraintSatisfied(false);
+        job.setTimingDelayConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
-        job.setTimingDelayConstraintSatisfied(true);
+        job.setTimingDelayConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
     }
 
@@ -366,15 +367,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
-        job.setDeadlineConstraintSatisfied(true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
-        job.setDeadlineConstraintSatisfied(true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
     }
 
@@ -387,15 +388,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setConnectivityConstraintSatisfied(false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
-        job.setConnectivityConstraintSatisfied(true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setConnectivityConstraintSatisfied(false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
-        job.setConnectivityConstraintSatisfied(true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
     }
 
@@ -410,15 +411,15 @@
         final JobStatus job = createJobStatus(jobInfo);
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
     }
 
@@ -436,15 +437,15 @@
 
         markImplicitConstraintsSatisfied(job, false);
 
-        job.setChargingConstraintSatisfied(false);
-        job.setConnectivityConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
-        job.setChargingConstraintSatisfied(true);
-        job.setConnectivityConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         // Still false because implicit constraints aren't satisfied.
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
@@ -452,61 +453,61 @@
 
         markImplicitConstraintsSatisfied(job, true);
 
-        job.setChargingConstraintSatisfied(false);
-        job.setConnectivityConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
         // Turn on constraints one at a time.
-        job.setChargingConstraintSatisfied(true);
-        job.setConnectivityConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setConnectivityConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setConnectivityConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
         // With two of the 3 constraints satisfied (and implicit constraints also satisfied), only
         // the unsatisfied constraint should return true.
-        job.setChargingConstraintSatisfied(true);
-        job.setConnectivityConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
-        job.setChargingConstraintSatisfied(true);
-        job.setConnectivityConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setConnectivityConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
 
-        job.setChargingConstraintSatisfied(true);
-        job.setConnectivityConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setConnectivityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
@@ -526,15 +527,15 @@
 
         markImplicitConstraintsSatisfied(job, false);
 
-        job.setChargingConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
-        job.setChargingConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
-        job.setDeadlineConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         // Still false because implicit constraints aren't satisfied.
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
@@ -542,18 +543,18 @@
 
         markImplicitConstraintsSatisfied(job, true);
 
-        job.setChargingConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
         // Turn on constraints one at a time.
-        job.setChargingConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(false);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         // Deadline should force isReady to be true, but isn't needed for the job to be
         // considered ready.
@@ -561,17 +562,17 @@
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(true);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(false);
-        job.setDeadlineConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         // Since the deadline constraint is satisfied, none of the other explicit constraints are
         // needed.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
@@ -581,33 +582,33 @@
 
         // With two of the 3 constraints satisfied (and implicit constraints also satisfied), only
         // the unsatisfied constraint should return true.
-        job.setChargingConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
-        job.setDeadlineConstraintSatisfied(false);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
-        job.setChargingConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(false);
-        job.setDeadlineConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
-        job.setChargingConstraintSatisfied(false);
-        job.setContentTriggerConstraintSatisfied(true);
-        job.setDeadlineConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
 
-        job.setChargingConstraintSatisfied(true);
-        job.setContentTriggerConstraintSatisfied(true);
-        job.setDeadlineConstraintSatisfied(true);
+        job.setChargingConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setContentTriggerConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+        job.setDeadlineConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
         // Once implicit constraint are satisfied, deadline constraint should always return true.
@@ -621,15 +622,15 @@
                 new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setDeviceNotDozingConstraintSatisfied(false, false);
+        job.setDeviceNotDozingConstraintSatisfied(sElapsedRealtimeClock.millis(), false, false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEVICE_NOT_DOZING));
-        job.setDeviceNotDozingConstraintSatisfied(true, false);
+        job.setDeviceNotDozingConstraintSatisfied(sElapsedRealtimeClock.millis(), true, false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEVICE_NOT_DOZING));
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setDeviceNotDozingConstraintSatisfied(false, false);
+        job.setDeviceNotDozingConstraintSatisfied(sElapsedRealtimeClock.millis(), false, false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEVICE_NOT_DOZING));
-        job.setDeviceNotDozingConstraintSatisfied(true, false);
+        job.setDeviceNotDozingConstraintSatisfied(sElapsedRealtimeClock.millis(), true, false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEVICE_NOT_DOZING));
     }
 
@@ -640,15 +641,15 @@
                 new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setQuotaConstraintSatisfied(false);
+        job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_WITHIN_QUOTA));
-        job.setQuotaConstraintSatisfied(true);
+        job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_WITHIN_QUOTA));
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setQuotaConstraintSatisfied(false);
+        job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_WITHIN_QUOTA));
-        job.setQuotaConstraintSatisfied(true);
+        job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_WITHIN_QUOTA));
     }
 
@@ -659,22 +660,24 @@
                 new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
 
         markImplicitConstraintsSatisfied(job, false);
-        job.setBackgroundNotRestrictedConstraintSatisfied(false);
+        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
-        job.setBackgroundNotRestrictedConstraintSatisfied(true);
+        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
 
         markImplicitConstraintsSatisfied(job, true);
-        job.setBackgroundNotRestrictedConstraintSatisfied(false);
+        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), false);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
-        job.setBackgroundNotRestrictedConstraintSatisfied(true);
+        job.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
     }
 
     private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
-        job.setQuotaConstraintSatisfied(isSatisfied);
-        job.setDeviceNotDozingConstraintSatisfied(isSatisfied, false);
-        job.setBackgroundNotRestrictedConstraintSatisfied(isSatisfied);
+        job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
+        job.setDeviceNotDozingConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), isSatisfied, false);
+        job.setBackgroundNotRestrictedConstraintSatisfied(
+                sElapsedRealtimeClock.millis(), isSatisfied);
     }
 
     private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index b72121f..88a691b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -359,8 +359,9 @@
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
         // Make sure Doze and background-not-restricted don't affect tests.
-        js.setDeviceNotDozingConstraintSatisfied(/* state */ true, /* allowlisted */false);
-        js.setBackgroundNotRestrictedConstraintSatisfied(true);
+        js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
+                /* state */ true, /* allowlisted */false);
+        js.setBackgroundNotRestrictedConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
         return js;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 41e1563..6630178 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -66,11 +66,13 @@
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpNoted(eq(AppOpsManager.OP_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpNoted(eq(AppOpsManager.OP_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
 
         // Stop watching
         appOpsManager.stopWatchingNoted(listener);
@@ -94,7 +96,8 @@
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(2)).onOpNoted(eq(AppOpsManager.OP_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
 
         // Finish up
         appOpsManager.stopWatchingNoted(listener);
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index fec8aa9..c12eb32 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -63,11 +63,13 @@
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
         inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
 
         // Stop watching
         appOpsManager.stopWatchingStarted(listener);
@@ -91,7 +93,8 @@
         verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
                 .times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
                 eq(Process.myUid()), eq(getContext().getPackageName()),
-                eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
+                eq(getContext().getAttributionTag()), eq(AppOpsManager.OP_FLAG_SELF),
+                eq(AppOpsManager.MODE_ALLOWED));
         verifyNoMoreInteractions(listener);
 
         // Finish up
diff --git a/services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java
new file mode 100644
index 0000000..ef20ee7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorManager;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.display.TestUtils;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Constructor;
+import java.time.Duration;
+
+public class FaceDownDetectorTest {
+    @ClassRule
+    public static final TestableContext sContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
+    private final FaceDownDetector mFaceDownDetector =
+            new FaceDownDetector(this::onFlip);
+
+    @Mock private SensorManager mSensorManager;
+
+    private long mCurrentTime;
+    private int mOnFaceDownCalls = 0;
+    private int mOnFaceDownExitCalls = 0;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        sContext.addMockSystemService(SensorManager.class, mSensorManager);
+        mCurrentTime = 0;
+    }
+
+    @Test
+    public void faceDownFor2Seconds_triggersFaceDown() throws Exception {
+        mFaceDownDetector.systemReady(sContext);
+
+        // Face up
+        // Using 0.5 on x to simulate constant acceleration, such as a sloped surface.
+        mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
+
+        for (int i = 0; i < 200; i++) {
+            advanceTime(Duration.ofMillis(20));
+            mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
+        }
+
+        assertThat(mOnFaceDownCalls).isEqualTo(1);
+        assertThat(mOnFaceDownExitCalls).isEqualTo(0);
+    }
+
+    @Test
+    public void faceDownFor2Seconds_withMotion_DoesNotTriggerFaceDown() throws Exception {
+        mFaceDownDetector.systemReady(sContext);
+
+        // Face up
+        mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
+
+        for (int i = 0; i < 100; i++) {
+            advanceTime(Duration.ofMillis(20));
+            //Move along x direction
+            mFaceDownDetector.onSensorChanged(createTestEvent(0.5f * i, 0.0f, -10.0f));
+        }
+
+        assertThat(mOnFaceDownCalls).isEqualTo(0);
+        assertThat(mOnFaceDownExitCalls).isEqualTo(0);
+    }
+
+    @Test
+    public void faceDownForHalfSecond_DoesNotTriggerFaceDown() throws Exception {
+        mFaceDownDetector.systemReady(sContext);
+
+        // Face up
+        mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
+
+        for (int i = 0; i < 100; i++) {
+            advanceTime(Duration.ofMillis(5));
+            mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
+        }
+
+        assertThat(mOnFaceDownCalls).isEqualTo(0);
+        assertThat(mOnFaceDownExitCalls).isEqualTo(0);
+    }
+
+    @Test
+    public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception {
+        mFaceDownDetector.systemReady(sContext);
+
+        // Face up
+        // Using 0.5 on x to simulate constant acceleration, such as a sloped surface.
+        mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
+
+        // Trigger face down
+        for (int i = 0; i < 100; i++) {
+            advanceTime(Duration.ofMillis(20));
+            mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
+        }
+
+        // Phone flips
+        for (int i = 0; i < 10; i++) {
+            advanceTime(Duration.ofMillis(5));
+            mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 1.0f, 0.0f));
+        }
+
+        assertThat(mOnFaceDownCalls).isEqualTo(1);
+        assertThat(mOnFaceDownExitCalls).isEqualTo(1);
+    }
+
+    private void advanceTime(Duration duration) {
+        mCurrentTime += duration.toNanos();
+    }
+
+    /**
+     * Create a test event to replicate an accelerometer sensor event.
+     * @param x Acceleration along the x dimension.
+     * @param y Acceleration along the y dimension.
+     * @param gravity Acceleration along the Z dimension. Relates to
+     */
+    private SensorEvent createTestEvent(float x, float y, float gravity) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(3);
+        event.sensor =
+                TestUtils.createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER);
+        event.values[0] = x;
+        event.values[1] = y;
+        event.values[2] = gravity;
+        event.timestamp = mCurrentTime;
+        return event;
+    }
+
+    private void onFlip(boolean isFaceDown) {
+        if (isFaceDown) {
+            mOnFaceDownCalls++;
+        } else {
+            mOnFaceDownExitCalls++;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
index 1c2d8e8..5012ca9 100644
--- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -209,7 +209,8 @@
     private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
         @Override
         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
-                SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+                SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                FaceDownDetector faceDownDetector) {
             return mNotifierMock;
         }
 
@@ -296,6 +297,7 @@
                 IBatteryStats.Stub.asInterface(ServiceManager.getService(
                         BatteryStats.SERVICE_NAME)),
                 mInjector.createSuspendBlocker(mService, "testBlocker"),
+                null,
                 null);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 592eea1..6b95cfc 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -211,7 +211,8 @@
         mService = new PowerManagerService(mContextSpy, new Injector() {
             @Override
             Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
-                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+                    SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
+                    FaceDownDetector faceDownDetector) {
                 return mNotifierMock;
             }
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 5574836..5f86d28 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -287,8 +287,7 @@
     }
 
     private static ExternalTimeSuggestion createExternalTimeSuggestion() {
-        TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
-        return new ExternalTimeSuggestion(timeValue);
+        return new ExternalTimeSuggestion(100L, 1_000_000L);
     }
 
     private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index daa1b25..f7a498b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -1483,11 +1483,8 @@
          * reference time.
          */
         ExternalTimeSuggestion generateExternalTimeSuggestion(Instant suggestedTime) {
-            TimestampedValue<Long> utcTime =
-                    new TimestampedValue<>(
-                            mFakeEnvironment.peekElapsedRealtimeMillis(),
+            return new ExternalTimeSuggestion(mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
-            return new ExternalTimeSuggestion(utcTime);
         }
 
         /**
diff --git a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
index b40d59c..57e95d7 100644
--- a/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/WatcherTest.java
@@ -22,6 +22,7 @@
 
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LongSparseArray;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -619,6 +620,129 @@
     }
 
     @Test
+    public void testWatchedLongSparseArray() {
+        final String name = "WatchedLongSparseArray";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test WatchedLongSparseArray
+        WatchedLongSparseArray<Leaf> array = new WatchedLongSparseArray<>();
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        leafA.tick();
+        tester.verify(0, "Updates with no registration");
+        tester.register();
+        tester.verify(0, "Updates with no registration");
+        leafA.tick();
+        tester.verify(1, "Updates with registration");
+        leafB.tick();
+        tester.verify(2, "Updates with registration");
+        array.remove(INDEX_B);
+        tester.verify(3, "Removed b");
+        leafB.tick();
+        tester.verify(3, "Updates with b not watched");
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafB);
+        tester.verify(5, "Added b twice");
+        leafB.tick();
+        tester.verify(6, "Changed b - single notification");
+        array.remove(INDEX_C);
+        tester.verify(7, "Removed first b");
+        leafB.tick();
+        tester.verify(8, "Changed b - single notification");
+        array.remove(INDEX_B);
+        tester.verify(9, "Removed second b");
+        leafB.tick();
+        tester.verify(9, "Updated leafB - no change");
+        array.clear();
+        tester.verify(10, "Cleared array");
+        leafB.tick();
+        tester.verify(10, "Change to b not in array");
+
+        // Special methods
+        array.put(INDEX_A, leafA);
+        array.put(INDEX_B, leafB);
+        array.put(INDEX_C, leafC);
+        tester.verify(13, "Added c");
+        leafC.tick();
+        tester.verify(14, "Ticked c");
+        array.setValueAt(array.indexOfKey(INDEX_C), leafD);
+        tester.verify(15, "Replaced c with d");
+        leafC.tick();
+        tester.verify(15, "Ticked c (c not registered)");
+        leafD.tick();
+        tester.verify(16, "Ticked d and c (c not registered)");
+        array.append(INDEX_D, leafC);
+        tester.verify(17, "Append c");
+        leafC.tick();
+        leafD.tick();
+        tester.verify(19, "Ticked d and c");
+        assertEquals("Verify four elements", 4, array.size());
+        // Figure out which elements are at which indices.
+        Leaf[] x = new Leaf[4];
+        for (int i = 0; i < 4; i++) {
+            x[i] = array.valueAt(i);
+        }
+        array.removeAt(1);
+        tester.verify(20, "Removed one element");
+        x[1].tick();
+        tester.verify(20, "Ticked one removed element");
+        x[2].tick();
+        tester.verify(21, "Ticked one remaining element");
+
+        // Snapshot
+        {
+            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
+            tester.verify(21, "Generate snapshot (no changes)");
+            // Verify that the snapshot is a proper copy of the source.
+            assertEquals(name + " snap same size",
+                         array.size(), arraySnap.size());
+            for (int i = 0; i < array.size(); i++) {
+                for (int j = 0; j < arraySnap.size(); j++) {
+                    assertTrue(name + " elements differ",
+                               array.valueAt(i) != arraySnap.valueAt(j));
+                }
+                assertTrue(name + " element copy",
+                           array.valueAt(i).equals(arraySnap.valueAt(i)));
+            }
+            leafD.tick();
+            tester.verify(22, "Tick after snapshot");
+            // Verify that the array snapshot is sealed
+            verifySealed(name, ()->arraySnap.put(INDEX_A, leafB));
+            assertTrue(!array.isSealed());
+            assertTrue(arraySnap.isSealed());
+        }
+        // Recreate the snapshot since the test corrupted it.
+        {
+            final WatchedLongSparseArray<Leaf> arraySnap = array.snapshot();
+            // Verify that elements are also snapshots
+            final Leaf arraySnapElement = arraySnap.valueAt(0);
+            verifySealed("ArraySnapshotElement", ()->arraySnapElement.tick());
+        }
+        // Verify copy-in/out
+        {
+            final String msg = name + " copy-in/out";
+            LongSparseArray<Leaf> base = new LongSparseArray<>();
+            array.copyTo(base);
+            WatchedLongSparseArray<Leaf> copy = new WatchedLongSparseArray<>();
+            copy.copyFrom(base);
+            final int end = array.size();
+            assertTrue(msg + " size mismatch " + end + " " + copy.size(), end == copy.size());
+            for (int i = 0; i < end; i++) {
+                final long key = array.keyAt(i);
+                assertTrue(msg, array.get(i) == copy.get(i));
+            }
+        }
+    }
+
+    @Test
     public void testWatchedSparseBooleanArray() {
         final String name = "WatchedSparseBooleanArray";
         WatchableTester tester;
@@ -733,4 +857,43 @@
             }
         }
     }
+
+    @Test
+    public void testNestedArrays() {
+        final String name = "NestedArrays";
+        WatchableTester tester;
+
+        // Create a few leaves
+        Leaf leafA = new Leaf();
+        Leaf leafB = new Leaf();
+        Leaf leafC = new Leaf();
+        Leaf leafD = new Leaf();
+
+        // Test nested arrays.
+        WatchedLongSparseArray<Leaf> lsaA = new WatchedLongSparseArray<>();
+        lsaA.put(2, leafA);
+        WatchedLongSparseArray<Leaf> lsaB = new WatchedLongSparseArray<>();
+        lsaB.put(4, leafB);
+        WatchedLongSparseArray<Leaf> lsaC = new WatchedLongSparseArray<>();
+        lsaC.put(6, leafC);
+
+        WatchedArrayMap<String, WatchedLongSparseArray<Leaf>> array =
+                new WatchedArrayMap<>();
+        array.put("A", lsaA);
+        array.put("B", lsaB);
+
+        // Test WatchedSparseIntArray
+        tester = new WatchableTester(array, name);
+        tester.verify(0, "Initial array - no registration");
+        tester.register();
+        tester.verify(0, "Initial array - post registration");
+        leafA.tick();
+        tester.verify(1, "tick grand-leaf");
+        lsaA.put(2, leafD);
+        tester.verify(2, "replace leafA");
+        leafA.tick();
+        tester.verify(2, "tick unregistered leafA");
+        leafD.tick();
+        tester.verify(3, "tick leafD");
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 781cfec..58169bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1489,7 +1489,7 @@
     }
 
     @Test
-    public void testClearIntermediateFixedRotation() throws RemoteException {
+    public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         mDisplayContent.setFixedRotationLaunchingApp(activity,
                 (mDisplayContent.getRotation() + 1) % 4);
@@ -1508,7 +1508,8 @@
                 ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
         verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
                 eq(activity.app.getThread()), adjustmentsCaptor.capture());
-        assertFalse(activity.hasFixedRotationTransform());
+        // The transformation is kept for animation in real case.
+        assertTrue(activity.hasFixedRotationTransform());
         final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
                 activity.token, null /* fixedRotationAdjustments */);
         // The captor may match other items. The first one must be the item to clear adjustments.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index b1ea4a5..5a0466a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -130,7 +130,7 @@
         // insets state with the global one.
         final InsetsState insetsState =
                 win.getDisplayContent().getInsetsStateController().getRawInsetsState();
-        win.mAboveInsetsState = insetsState;
+        win.mAboveInsetsState.set(insetsState);
     }
 
     public void setRotation(int rotation, boolean includingWindows) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 2163661..47cf53b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -18,6 +18,7 @@
 
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -37,6 +38,7 @@
 
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -300,9 +302,9 @@
         displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
         mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
-        mImeWindow.mAboveInsetsState = state;
+        mImeWindow.mAboveInsetsState.set(state);
         mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
-                state, displayInfo, null /* displayCutout */, null /* roundedCorners*/);
+                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS);
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 20775e8..683ed88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -117,7 +117,7 @@
     static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
             boolean withDisplayCutout, boolean isLongEdgeCutout) {
         final DisplayInfo info = new DisplayInfo();
-        WmDisplayCutout cutout = null;
+        WmDisplayCutout cutout = WmDisplayCutout.NO_CUTOUT;
 
         final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
         info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index ee293fc..be03603 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,13 +32,14 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -208,7 +209,7 @@
         app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
 
         // Make sure app got notified.
-        verify(app, atLeast(1)).notifyInsetsChanged();
+        verify(app, atLeastOnce()).notifyInsetsChanged();
 
         // app will get visible IME insets while below IME.
         assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
@@ -336,6 +337,92 @@
         assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
     }
 
+    @Test
+    public void testUpdateAboveInsetsState_provideInsets() {
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+
+        getController().updateAboveInsetsState(statusBar, true /* notifyInsetsChange */);
+
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    @Test
+    public void testUpdateAboveInsetsState_receiveInsets() {
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        getController().updateAboveInsetsState(app, true /* notifyInsetsChange */);
+
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    @Test
+    public void testUpdateAboveInsetsState_zOrderChanged() {
+        final WindowState ime = createTestWindow("ime");
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
+        getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+        getController().updateAboveInsetsState(ime, false /* notifyInsetsChange */);
+        getController().updateAboveInsetsState(statusBar, false /* notifyInsetsChange */);
+        getController().updateAboveInsetsState(navBar, false /* notifyInsetsChange */);
+
+        // ime is below others.
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        ime.getParent().positionChildAt(POSITION_TOP, ime, true /* includingParents */);
+        getController().updateAboveInsetsState(ime, true /* notifyInsetsChange */);
+
+        // ime is above others.
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        verify(ime, atLeastOnce()).notifyInsetsChanged();
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+        verify(statusBar, atLeastOnce()).notifyInsetsChanged();
+        verify(navBar, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    private WindowState createTestWindow(String name) {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, name);
+        win.setHasSurface(true);
+        spyOn(win);
+        return win;
+    }
+
     private InsetsStateController getController() {
         return mDisplayContent.getInsetsStateController();
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d545c65..f64f428 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11293,21 +11293,16 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
-     * Additionally, depending on the level of location permissions the caller holds (i.e. no
-     * location permissions, {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}, or
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}), location-sensitive fields will
-     * be cleared from the return value.
-     *
-     * <p>Note also that if the caller holds any sort of location permission, a usage event for the
-     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION} or
-     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION}
-     * will be logged against the caller when calling this method.
+     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
      *
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
-    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(allOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.ACCESS_COARSE_LOCATION
+    })
     public @Nullable ServiceState getServiceState() {
         return getServiceStateForSubscriber(getSubId());
     }
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index a9dae89..9bb4db8 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -282,6 +282,15 @@
     public static final String EXTRA_PICTURE_URL = "android.telephony.ims.extra.PICTURE_URL";
 
     /**
+     * Boolean extra indicating whether the call is a business call.
+     *
+     * This extra will be set to {@code true} if and only if the SIP INVITE headers contain the
+     * "Organization" header.
+     */
+    public static final String EXTRA_IS_BUSINESS_CALL =
+            "android.telephony.ims.extra.IS_BUSINESS_CALL";
+
+    /**
      * Values for EXTRA_OIR / EXTRA_CNAP
      */
     /**
@@ -791,7 +800,9 @@
                 + ", emergencyCallTesting=" + mEmergencyCallTesting
                 + ", hasKnownUserIntentEmergency=" + mHasKnownUserIntentEmergency
                 + ", mRestrictCause=" + mRestrictCause
-                + ", mCallerNumberVerstat= " + mCallerNumberVerificationStatus + " }";
+                + ", mCallerNumberVerstat= " + mCallerNumberVerificationStatus
+                + ", mAcceptedRtpHeaderExtensions= " + mAcceptedRtpHeaderExtensionTypes
+                + " }";
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RtpHeaderExtension.java b/telephony/java/android/telephony/ims/RtpHeaderExtension.java
index f9ab701..8815cef 100644
--- a/telephony/java/android/telephony/ims/RtpHeaderExtension.java
+++ b/telephony/java/android/telephony/ims/RtpHeaderExtension.java
@@ -134,4 +134,19 @@
         result = 31 * result + Arrays.hashCode(mExtensionData);
         return result;
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("RtpHeaderExtension{mLocalIdentifier=");
+        sb.append(mLocalIdentifier);
+        sb.append(", mData=");
+        for (byte b : mExtensionData) {
+            sb.append(Integer.toBinaryString(b));
+            sb.append("b_");
+        }
+        sb.append("}");
+
+        return sb.toString();
+    }
 }
diff --git a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
index e1d39c2..af4e2347 100644
--- a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
+++ b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
@@ -133,4 +133,16 @@
     public int hashCode() {
         return Objects.hash(mLocalIdentifier, mUri);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("RtpHeaderExtensionType{mLocalIdentifier=");
+        sb.append(mLocalIdentifier);
+        sb.append(", mUri=");
+        sb.append(mUri);
+        sb.append("}");
+
+        return sb.toString();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 1d04953..5c25524 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2340,6 +2340,11 @@
     void sendDeviceToDeviceMessage(int message, int value);
 
     /**
+     * Sets the specified transport active; only for use through shell.
+     */
+    void setActiveDeviceToDeviceTransport(String transport);
+
+    /**
      * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription.
      */
     boolean getCarrierSingleRegistrationEnabled(int subId);
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index ab3572b..d96005b 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -95,10 +95,13 @@
             new AddFsVerityCertRule(this, CERT_PATH);
 
     private ITestDevice mDevice;
+    private boolean mDmRequireFsVerity;
 
     @Before
     public void setUp() throws DeviceNotAvailableException {
         mDevice = getDevice();
+        mDmRequireFsVerity = "true".equals(
+                mDevice.getProperty("pm.dexopt.dm.require_fsverity"));
 
         uninstallPackage(TARGET_PACKAGE);
     }
@@ -124,7 +127,7 @@
         verifyInstalledFiles(
                 INSTALLED_BASE_APK,
                 INSTALLED_BASE_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity();
+        verifyInstalledFilesHaveFsverity(INSTALLED_BASE_APK);
     }
 
     @Test
@@ -151,7 +154,9 @@
                 INSTALLED_BASE_APK_FSV_SIG,
                 INSTALLED_SPLIT_APK,
                 INSTALLED_SPLIT_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity();
+        verifyInstalledFilesHaveFsverity(
+                INSTALLED_BASE_APK,
+                INSTALLED_SPLIT_APK);
     }
 
     @Test
@@ -167,7 +172,9 @@
                 INSTALLED_BASE_APK_FSV_SIG,
                 INSTALLED_BASE_DM,
                 INSTALLED_BASE_DM_FSV_SIG);
-        verifyInstalledFilesHaveFsverity();
+        verifyInstalledFilesHaveFsverity(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_DM);
     }
 
     @Test
@@ -189,7 +196,11 @@
                 INSTALLED_SPLIT_APK_FSV_SIG,
                 INSTALLED_SPLIT_DM,
                 INSTALLED_SPLIT_DM_FSV_SIG);
-        verifyInstalledFilesHaveFsverity();
+        verifyInstalledFilesHaveFsverity(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_DM,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_DM);
     }
 
     @Test
@@ -213,7 +224,9 @@
                 INSTALLED_BASE_APK_FSV_SIG,
                 INSTALLED_SPLIT_APK,
                 INSTALLED_SPLIT_APK_FSV_SIG);
-        verifyInstalledFilesHaveFsverity();
+        verifyInstalledFilesHaveFsverity(
+                INSTALLED_BASE_APK,
+                INSTALLED_SPLIT_APK);
     }
 
     @Test
@@ -250,18 +263,6 @@
                 INSTALLED_BASE_APK,
                 INSTALLED_SPLIT_APK,
                 INSTALLED_SPLIT_APK_FSV_SIG);
-
-    }
-
-    @Test
-    public void testInstallOnlyBaseHasFsvSig()
-            throws DeviceNotAvailableException, FileNotFoundException {
-        new InstallMultiple()
-                .addFileAndSignature(BASE_APK)
-                .addFile(BASE_APK_DM)
-                .addFile(SPLIT_APK)
-                .addFile(SPLIT_APK_DM)
-                .runExpectingFailure();
     }
 
     @Test
@@ -271,18 +272,83 @@
                 .addFile(BASE_APK)
                 .addFileAndSignature(BASE_APK_DM)
                 .addFile(SPLIT_APK)
-                .addFile(SPLIT_APK_DM)
+                .addFileAndSignature(SPLIT_APK_DM)
+                .run();
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_DM,
+                INSTALLED_BASE_DM_FSV_SIG,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_DM,
+                INSTALLED_SPLIT_DM_FSV_SIG);
+        verifyInstalledFilesHaveFsverity(
+                INSTALLED_BASE_DM,
+                INSTALLED_SPLIT_DM);
+    }
+
+    @Test
+    public void testInstallDmWithoutFsvSig_Base()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        InstallMultiple installer = new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFile(BASE_APK_DM)
+                .addFile(SPLIT_APK)
+                .addFileAndSignature(SPLIT_APK_DM);
+        if (mDmRequireFsVerity) {
+            installer.runExpectingFailure();
+        } else {
+            installer.run();
+            verifyInstalledFiles(
+                    INSTALLED_BASE_APK,
+                    INSTALLED_BASE_DM,
+                    INSTALLED_SPLIT_APK,
+                    INSTALLED_SPLIT_DM,
+                    INSTALLED_SPLIT_DM_FSV_SIG);
+            verifyInstalledFilesHaveFsverity(INSTALLED_SPLIT_DM);
+        }
+    }
+
+    @Test
+    public void testInstallDmWithoutFsvSig_Split()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        InstallMultiple installer = new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFileAndSignature(BASE_APK_DM)
+                .addFile(SPLIT_APK)
+                .addFile(SPLIT_APK_DM);
+        if (mDmRequireFsVerity) {
+            installer.runExpectingFailure();
+        } else {
+            installer.run();
+            verifyInstalledFiles(
+                    INSTALLED_BASE_APK,
+                    INSTALLED_BASE_DM,
+                    INSTALLED_BASE_DM_FSV_SIG,
+                    INSTALLED_SPLIT_APK,
+                    INSTALLED_SPLIT_DM);
+            verifyInstalledFilesHaveFsverity(INSTALLED_BASE_DM);
+        }
+    }
+
+    @Test
+    public void testInstallSomeApkIsMissingFsvSig_Base()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .addFileAndSignature(BASE_APK_DM)
+                .addFile(SPLIT_APK)
+                .addFileAndSignature(SPLIT_APK_DM)
                 .runExpectingFailure();
     }
 
     @Test
-    public void testInstallOnlySplitHasFsvSig()
+    public void testInstallSomeApkIsMissingFsvSig_Split()
             throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
                 .addFile(BASE_APK)
-                .addFile(BASE_APK_DM)
+                .addFileAndSignature(BASE_APK_DM)
                 .addFileAndSignature(SPLIT_APK)
-                .addFile(SPLIT_APK_DM)
+                .addFileAndSignature(SPLIT_APK_DM)
                 .runExpectingFailure();
     }
 
@@ -383,37 +449,36 @@
         }
     }
 
-    private void verifyInstalledFilesHaveFsverity() throws DeviceNotAvailableException {
+    private void verifyInstalledFilesHaveFsverity(String... filenames)
+            throws DeviceNotAvailableException {
         // Verify that all files are protected by fs-verity
         String apkPath = getApkPath(TARGET_PACKAGE);
         String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
         long kTargetOffset = 0;
-        for (String basename : expectRemoteCommandToSucceed("ls " + appDir).split("\n")) {
-            if (basename.endsWith(".apk") || basename.endsWith(".dm")) {
-                String path = appDir + "/" + basename;
-                damageFileAgainstBlockDevice(path, kTargetOffset);
+        for (String basename : filenames) {
+            String path = appDir + "/" + basename;
+            damageFileAgainstBlockDevice(path, kTargetOffset);
 
-                // Retry is sometimes needed to pass the test. Package manager may have FD leaks
-                // (see b/122744005 as example) that prevents the file in question to be evicted
-                // from filesystem cache. Forcing GC workarounds the problem.
-                int retry = 5;
-                for (; retry > 0; retry--) {
-                    BlockDeviceWriter.dropCaches(mDevice);
-                    if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
-                        break;
-                    }
-                    try {
-                        CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath));
-                        Thread.sleep(1000);
-                        String pid = expectRemoteCommandToSucceed("pidof system_server");
-                        mDevice.executeShellV2Command("kill -10 " + pid);  // force GC
-                    } catch (InterruptedException e) {
-                        Thread.currentThread().interrupt();
-                        return;
-                    }
+            // Retry is sometimes needed to pass the test. Package manager may have FD leaks
+            // (see b/122744005 as example) that prevents the file in question to be evicted
+            // from filesystem cache. Forcing GC workarounds the problem.
+            int retry = 5;
+            for (; retry > 0; retry--) {
+                BlockDeviceWriter.dropCaches(mDevice);
+                if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
+                    break;
                 }
-                assertTrue("Read from " + path + " should fail", retry > 0);
+                try {
+                    CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath));
+                    Thread.sleep(1000);
+                    String pid = expectRemoteCommandToSucceed("pidof system_server");
+                    mDevice.executeShellV2Command("kill -10 " + pid);  // force GC
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return;
+                }
             }
+            assertTrue("Read from " + path + " should fail", retry > 0);
         }
     }
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c6c67fe..62ccb1a0 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -400,6 +400,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="StretchySurfaceViewActivity"
+                  android:label="SurfaceView/Stretchy Movement"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="GetBitmapSurfaceViewActivity"
              android:label="SurfaceView/GetBitmap with Camera source"
              android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
index 818d899..65d7363 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PositionListenerActivity.java
@@ -19,8 +19,13 @@
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.RenderNode;
 import android.os.Bundle;
+import android.view.MotionEvent;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.ScrollView;
@@ -38,7 +43,46 @@
         ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
         layout.addView(spinner);
 
-        ScrollView scrollingThing = new ScrollView(this);
+        ScrollView scrollingThing = new ScrollView(this) {
+            int setting = 0;
+            PointF opts[] = new PointF[] {
+                    new PointF(0, 0),
+                    new PointF(0, -1f),
+                    new PointF(1f, 0),
+                    new PointF(0, 1f),
+                    new PointF(-1f, 0),
+                    new PointF(-1f, 1f),
+            };
+            {
+                setWillNotDraw(false);
+            }
+
+            @Override
+            public boolean onTouchEvent(MotionEvent ev) {
+                if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+                    setting = (setting + 1) % opts.length;
+                    invalidate();
+                }
+                return super.onTouchEvent(ev);
+            }
+
+            @Override
+            protected void onDraw(Canvas canvas) {
+                super.onDraw(canvas);
+                RenderNode node = ((RecordingCanvas) canvas).mNode;
+                PointF dir = opts[setting];
+                float maxStretchAmount = 100f;
+                // Although we could do this in a single call, the real one won't be - so mimic that
+                if (dir.x != 0f) {
+                    node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
+                            dir.x, 0f, maxStretchAmount);
+                }
+                if (dir.y != 0f) {
+                    node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
+                            0f, dir.y, maxStretchAmount);
+                }
+            }
+        };
         scrollingThing.addView(new MyPositionReporter(this));
         layout.addView(scrollingThing);
 
@@ -49,6 +93,11 @@
         RenderNode mNode;
         int mCurrentCount = 0;
         int mTranslateY = 0;
+        Rect mPosition = new Rect();
+        RectF mStretchArea = new RectF();
+        float mStretchX = 0.0f;
+        float mStretchY = 0.0f;
+        float mStretchMax = 0.0f;
 
         MyPositionReporter(Context c) {
             super(c);
@@ -78,18 +127,36 @@
             canvas.drawRenderNode(mNode);
         }
 
+        void updateText() {
+            setText(String.format("%d: Position %s, stretch area %s, vec %f,%f, amount %f",
+                    mCurrentCount, mPosition.toShortString(), mStretchArea.toShortString(),
+                    mStretchX, mStretchY, mStretchMax));
+        }
+
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
-            post(() -> {
+            getHandler().postAtFrontOfQueue(() -> {
                 mCurrentCount++;
-                setText(String.format("%d: Position [%d, %d, %d, %d]", mCurrentCount,
-                        left, top, right, bottom));
+                mPosition.set(left, top, right, bottom);
+                updateText();
+            });
+        }
+
+        @Override
+        public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
+                float vecX, float vecY, float maxStretch) {
+            getHandler().postAtFrontOfQueue(() -> {
+                mStretchArea.set(left, top, right, bottom);
+                mStretchX = vecX;
+                mStretchY = vecY;
+                mStretchMax = maxStretch;
+                updateText();
             });
         }
 
         @Override
         public void positionLost(long frameNumber) {
-            post(() -> {
+            getHandler().postAtFrontOfQueue(() -> {
                 mCurrentCount++;
                 setText(mCurrentCount + " No position");
             });
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java
new file mode 100644
index 0000000..d604244
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchySurfaceViewActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.RenderNode;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.SurfaceView;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+
+public class StretchySurfaceViewActivity extends Activity implements Callback {
+    SurfaceView mSurfaceView;
+    ObjectAnimator mAnimator;
+
+    class MySurfaceView extends SurfaceView {
+        boolean mSlow;
+        boolean mScaled;
+        int mToggle = 0;
+
+        public MySurfaceView(Context context) {
+            super(context);
+            setOnClickListener(v -> {
+                mToggle = (mToggle + 1) % 4;
+                mSlow = (mToggle & 0x2) != 0;
+                mScaled = (mToggle & 0x1) != 0;
+
+                mSurfaceView.setScaleX(mScaled ? 1.6f : 1f);
+                mSurfaceView.setScaleY(mScaled ? 0.8f : 1f);
+
+                setTitle("Slow=" + mSlow + ", scaled=" + mScaled);
+                invalidate();
+            });
+            setWillNotDraw(false);
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            super.draw(canvas);
+            if (mSlow) {
+                try {
+                    Thread.sleep(16);
+                } catch (InterruptedException e) {}
+            }
+        }
+
+        public void setMyTranslationY(float ty) {
+            setTranslationY(ty);
+            if (mSlow) {
+                invalidate();
+            }
+        }
+
+        public float getMyTranslationY() {
+            return getTranslationY();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout content = new FrameLayout(this) {
+            {
+                setWillNotDraw(false);
+            }
+
+            @Override
+            protected void onDraw(Canvas canvas) {
+                Paint paint = new Paint();
+                paint.setAntiAlias(true);
+                paint.setColor(Color.RED);
+                paint.setStyle(Paint.Style.STROKE);
+                paint.setStrokeWidth(10f);
+                canvas.drawLine(0f, 0f, getWidth(), getHeight(), paint);
+                super.onDraw(canvas);
+
+                RenderNode node = ((RecordingCanvas) canvas).mNode;
+                node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f, 1f, 400f);
+            }
+        };
+
+        mSurfaceView = new MySurfaceView(this);
+        mSurfaceView.getHolder().addCallback(this);
+
+        final float density = getResources().getDisplayMetrics().density;
+        int size = (int) (200 * density);
+
+        content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                size, size, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+        mAnimator = ObjectAnimator.ofFloat(mSurfaceView, "myTranslationY",
+                0, size);
+        mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+        mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+        mAnimator.setDuration(1000);
+        mAnimator.setInterpolator(new LinearInterpolator());
+        setContentView(content);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Canvas canvas = holder.lockCanvas();
+        canvas.drawColor(Color.WHITE);
+
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setColor(Color.GREEN);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(10f);
+        canvas.drawLine(0, 0, width, height, paint);
+
+        holder.unlockCanvasAndPost(canvas);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mAnimator.start();
+    }
+
+    @Override
+    protected void onPause() {
+        mAnimator.pause();
+        super.onPause();
+    }
+}