Merge "Added data retry manager and retry rules"
diff --git a/Android.bp b/Android.bp
index a5367fe..0c1095d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -249,6 +249,12 @@
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
+        "android.hardware.radio.data-V1-java",
+        "android.hardware.radio.messaging-V1-java",
+        "android.hardware.radio.modem-V1-java",
+        "android.hardware.radio.network-V1-java",
+        "android.hardware.radio.sim-V1-java",
+        "android.hardware.radio.voice-V1-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
diff --git a/OWNERS b/OWNERS
index ab5bd18..47d8c83 100644
--- a/OWNERS
+++ b/OWNERS
@@ -29,10 +29,7 @@
 # Support bulk translation updates
 per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
 
-per-file Android.bp = file:platform/build/soong:/OWNERS
-per-file Android.mk = file:platform/build/soong:/OWNERS
-per-file ApiDocs.bp = file:platform/build/soong:/OWNERS
-per-file StubLibraries.bp = file:platform/build/soong:/OWNERS
-per-file ProtoLibraries.bp = file:platform/build/soong:/OWNERS
+per-file *.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
+per-file Android.mk = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
 per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
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 4ea46eb..1016294 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
@@ -56,7 +56,6 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseArrayMap;
@@ -76,11 +75,11 @@
 import com.android.server.job.StateControllerProto;
 import com.android.server.usage.AppStandbyInternal;
 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import com.android.server.utils.AlarmQueue;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.PriorityQueue;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -323,10 +322,10 @@
             new SparseArrayMap<>();
 
     /**
-     * Listener to track and manage when each package comes back within quota.
+     * Queue to track and manage when each package comes back within quota.
      */
     @GuardedBy("mLock")
-    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
+    private final InQuotaAlarmQueue mInQuotaAlarmQueue;
 
     /** Cached calculation results for each app, with the standby buckets as the array indices. */
     private final SparseArrayMap<String, ExecutionStats[]> mExecutionStatsCache =
@@ -589,11 +588,12 @@
         mHandler = new QcHandler(mContext.getMainLooper());
         mChargeTracker = new ChargingTracker();
         mChargeTracker.startTracking();
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
         mQcConstants = new QcConstants();
         mBackgroundJobsController = backgroundJobsController;
         mConnectivityController = connectivityController;
         mIsEnabled = !mConstants.USE_TARE_POLICY;
+        mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper());
 
         // Set up the app standby bucketing tracker
         AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
@@ -742,7 +742,7 @@
         mEJPkgTimers.delete(userId);
         mTimingSessions.delete(userId);
         mEJTimingSessions.delete(userId);
-        mInQuotaAlarmListener.removeAlarmsLocked(userId);
+        mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
         mExecutionStatsCache.delete(userId);
         mEJStats.delete(userId);
         mSystemInstallers.remove(userId);
@@ -768,7 +768,7 @@
         }
         mTimingSessions.delete(userId, packageName);
         mEJTimingSessions.delete(userId, packageName);
-        mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
+        mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
         mExecutionStatsCache.delete(userId, packageName);
         mEJStats.delete(userId, packageName);
         mTopAppTrackers.delete(userId, packageName);
@@ -1657,7 +1657,7 @@
             // exempted.
             maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
         } else {
-            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
+            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
         }
         return changed;
     }
@@ -1695,7 +1695,7 @@
             if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
                 // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
                 // that all jobs for the userId-package are within quota.
-                mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
+                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
             } else {
                 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
             }
@@ -1760,7 +1760,7 @@
                         + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                         + "ms in its quota.");
             }
-            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
+            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
@@ -1825,7 +1825,7 @@
                             + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
             inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
         }
-        mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
+        mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed);
     }
 
     private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
@@ -2805,176 +2805,25 @@
         }
     }
 
-    static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
-        AlarmQueue() {
-            super(1, (o1, o2) -> (int) (o1.second - o2.second));
-        }
-
-        /**
-         * Remove any instances of the Package from the queue.
-         *
-         * @return true if an instance was removed, false otherwise.
-         */
-        boolean remove(@NonNull Package pkg) {
-            boolean removed = false;
-            Pair[] alarms = toArray(new Pair[size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                if (pkg.equals(alarms[i].first)) {
-                    remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            return removed;
-        }
-    }
-
     /** Track when UPTCs are expected to come back into quota. */
-    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
-        @GuardedBy("mLock")
-        private final AlarmQueue mAlarmQueue = new AlarmQueue();
-        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
-        @GuardedBy("mLock")
-        private long mTriggerTimeElapsed = 0;
-        /** The minimum amount of time between quota check alarms. */
-        @GuardedBy("mLock")
-        private long mMinQuotaCheckDelayMs = QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS;
-
-        @GuardedBy("mLock")
-        void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) {
-            final Package pkg = new Package(userId, pkgName);
-            mAlarmQueue.remove(pkg);
-            mAlarmQueue.offer(new Pair<>(pkg, inQuotaTimeElapsed));
-            setNextAlarmLocked();
-        }
-
-        @GuardedBy("mLock")
-        void setMinQuotaCheckDelayMs(long minDelayMs) {
-            mMinQuotaCheckDelayMs = minDelayMs;
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmLocked(@NonNull Package pkg) {
-            if (mAlarmQueue.remove(pkg)) {
-                setNextAlarmLocked();
-            }
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmLocked(int userId, @NonNull String packageName) {
-            removeAlarmLocked(new Package(userId, packageName));
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmsLocked(int userId) {
-            boolean removed = false;
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                final Package pkg = (Package) alarms[i].first;
-                if (userId == pkg.userId) {
-                    mAlarmQueue.remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                setNextAlarmLocked();
-            }
-        }
-
-        @GuardedBy("mLock")
-        private void setNextAlarmLocked() {
-            setNextAlarmLocked(sElapsedRealtimeClock.millis());
-        }
-
-        @GuardedBy("mLock")
-        private void setNextAlarmLocked(long earliestTriggerElapsed) {
-            if (mAlarmQueue.size() > 0) {
-                final Pair<Package, Long> alarm = mAlarmQueue.peek();
-                final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second);
-                // Only schedule the alarm if one of the following is true:
-                // 1. There isn't one currently scheduled
-                // 2. The new alarm is significantly earlier than the previous alarm. If it's
-                // earlier but not significantly so, then we essentially delay the job a few extra
-                // minutes.
-                // 3. The alarm is after the current alarm.
-                if (mTriggerTimeElapsed == 0
-                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
-                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed
-                                + " for app " + alarm.first);
-                    }
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
-                            ALARM_TAG_QUOTA_CHECK, this, mHandler);
-                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
-                }
-            } else {
-                mAlarmManager.cancel(this);
-                mTriggerTimeElapsed = 0;
-            }
+    private class InQuotaAlarmQueue extends AlarmQueue<Package> {
+        private InQuotaAlarmQueue(Context context, Looper looper) {
+            super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false,
+                    QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
         }
 
         @Override
-        public void onAlarm() {
-            synchronized (mLock) {
-                while (mAlarmQueue.size() > 0) {
-                    final Pair<Package, Long> alarm = mAlarmQueue.peek();
-                    if (alarm.second <= sElapsedRealtimeClock.millis()) {
-                        mHandler.obtainMessage(MSG_CHECK_PACKAGE, alarm.first.userId, 0,
-                                alarm.first.packageName).sendToTarget();
-                        mAlarmQueue.remove(alarm);
-                    } else {
-                        break;
-                    }
-                }
-                setNextAlarmLocked(sElapsedRealtimeClock.millis() + mMinQuotaCheckDelayMs);
-            }
+        protected boolean isForUser(@NonNull Package key, int userId) {
+            return key.userId == userId;
         }
 
-        @GuardedBy("mLock")
-        void dumpLocked(IndentingPrintWriter pw) {
-            pw.println("In quota alarms:");
-            pw.increaseIndent();
-
-            if (mAlarmQueue.size() == 0) {
-                pw.println("NOT WAITING");
-            } else {
-                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-                for (int i = 0; i < alarms.length; ++i) {
-                    final Package pkg = (Package) alarms[i].first;
-                    pw.print(pkg);
-                    pw.print(": ");
-                    pw.print(alarms[i].second);
-                    pw.println();
-                }
+        @Override
+        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+            for (int i = 0; i < expired.size(); ++i) {
+                Package p = expired.valueAt(i);
+                mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName)
+                        .sendToTarget();
             }
-
-            pw.decreaseIndent();
-        }
-
-        @GuardedBy("mLock")
-        void dumpLocked(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(
-                    StateControllerProto.QuotaController.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
-                    mTriggerTimeElapsed);
-
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = 0; i < alarms.length; ++i) {
-                final long aToken = proto.start(
-                        StateControllerProto.QuotaController.InQuotaAlarmListener.ALARMS);
-
-                final Package pkg = (Package) alarms[i].first;
-                pkg.dumpDebug(proto,
-                        StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.PKG);
-                proto.write(
-                        StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
-                        (Long) alarms[i].second);
-
-                proto.end(aToken);
-            }
-
-            proto.end(token);
         }
     }
 
@@ -3563,7 +3412,7 @@
                             properties.getLong(key, DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
                     // We don't need to re-evaluate execution stats or constraint status for this.
                     // Limit the delay to the range [0, 15] minutes.
-                    mInQuotaAlarmListener.setMinQuotaCheckDelayMs(
+                    mInQuotaAlarmQueue.setMinTimeBetweenAlarmsMs(
                             Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
                     break;
                 case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS:
@@ -4104,7 +3953,7 @@
 
     @VisibleForTesting
     long getMinQuotaCheckDelayMs() {
-        return mInQuotaAlarmListener.mMinQuotaCheckDelayMs;
+        return mInQuotaAlarmQueue.getMinTimeBetweenAlarmsMs();
     }
 
     @VisibleForTesting
@@ -4302,7 +4151,7 @@
         pw.decreaseIndent();
 
         pw.println();
-        mInQuotaAlarmListener.dumpLocked(pw);
+        mInQuotaAlarmQueue.dump(pw);
         pw.decreaseIndent();
     }
 
@@ -4438,9 +4287,6 @@
             }
         }
 
-        mInQuotaAlarmListener.dumpLocked(proto,
-                StateControllerProto.QuotaController.IN_QUOTA_ALARM_LISTENER);
-
         proto.end(mToken);
         proto.end(token);
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index ad56d93..035c9d2 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -17,7 +17,6 @@
 package com.android.server.tare;
 
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
 import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
@@ -32,7 +31,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AlarmManager;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.os.Handler;
@@ -43,7 +42,6 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArrayMap;
 
@@ -52,13 +50,13 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
+import com.android.server.utils.AlarmQueue;
 
 import libcore.util.EmptyArray;
 
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
-import java.util.PriorityQueue;
 import java.util.function.Consumer;
 
 /**
@@ -103,12 +101,11 @@
             mActionAffordabilityNotes = new SparseArrayMap<>();
 
     /**
-     * Listener to track and manage when apps will cross the closest affordability threshold (in
+     * Queue to track and manage when apps will cross the closest affordability threshold (in
      * both directions).
      */
     @GuardedBy("mLock")
-    private final BalanceThresholdAlarmListener mBalanceThresholdAlarmListener =
-            new BalanceThresholdAlarmListener();
+    private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue;
 
     /**
      * Comparator to use to sort apps before we distribute ARCs so that we try to give the most
@@ -159,7 +156,6 @@
             };
 
     private static final int MSG_CHECK_BALANCE = 0;
-    private static final int MSG_SET_BALANCE_ALARM = 1;
 
     Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
         mLock = irs.getLock();
@@ -167,6 +163,8 @@
         mScribe = scribe;
         mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
         mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
+        mBalanceThresholdAlarmQueue = new BalanceThresholdAlarmQueue(
+                mIrs.getContext(), TareHandlerThread.get().getLooper());
     }
 
     private class TotalDeltaCalculator implements Consumer<OngoingEvent> {
@@ -692,7 +690,7 @@
     @GuardedBy("mLock")
     void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) {
         reclaimAssetsLocked(userId, pkgName);
-        mBalanceThresholdAlarmListener.removeAlarmLocked(userId, pkgName);
+        mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
     }
 
     /**
@@ -712,7 +710,7 @@
     @GuardedBy("mLock")
     void onUserRemovedLocked(final int userId, @NonNull final List<String> pkgNames) {
         reclaimAssetsLocked(userId, pkgNames);
-        mBalanceThresholdAlarmListener.removeAlarmsLocked(userId);
+        mBalanceThresholdAlarmQueue.removeAlarmsForUserId(userId);
     }
 
     @GuardedBy("mLock")
@@ -817,7 +815,7 @@
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents == null) {
             // No ongoing transactions. No reason to schedule
-            mBalanceThresholdAlarmListener.removeAlarmLocked(userId, pkgName);
+            mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
             return;
         }
         mTrendCalculator.reset(
@@ -829,7 +827,7 @@
         if (lowerTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
             if (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
                 // Will never cross a threshold based on current events.
-                mBalanceThresholdAlarmListener.removeAlarmLocked(userId, pkgName);
+                mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
                 return;
             }
             timeToThresholdMs = upperTimeMs;
@@ -837,14 +835,14 @@
             timeToThresholdMs = (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD)
                     ? lowerTimeMs : Math.min(lowerTimeMs, upperTimeMs);
         }
-        mBalanceThresholdAlarmListener.addAlarmLocked(userId, pkgName,
+        mBalanceThresholdAlarmQueue.addAlarm(new Package(userId, pkgName),
                 SystemClock.elapsedRealtime() + timeToThresholdMs);
     }
 
     @GuardedBy("mLock")
     void tearDownLocked() {
         mCurrentOngoingEvents.clear();
-        mBalanceThresholdAlarmListener.dropAllAlarmsLocked();
+        mBalanceThresholdAlarmQueue.removeAllAlarms();
     }
 
     @VisibleForTesting
@@ -869,261 +867,60 @@
         }
     }
 
-    /**
-     * An {@link AlarmManager.OnAlarmListener} that will queue up all pending alarms and only
-     * schedule one alarm for the earliest alarm.
-     */
-    private abstract class AlarmQueueListener implements AlarmManager.OnAlarmListener {
-        final class Package {
-            public final String packageName;
-            public final int userId;
+    private static final class Package {
+        public final String packageName;
+        public final int userId;
 
-            Package(int userId, String packageName) {
-                this.userId = userId;
-                this.packageName = packageName;
-            }
-
-            @Override
-            public String toString() {
-                return appToString(userId, packageName);
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (obj == null) {
-                    return false;
-                }
-                if (this == obj) {
-                    return true;
-                }
-                if (obj instanceof Package) {
-                    Package other = (Package) obj;
-                    return userId == other.userId && Objects.equals(packageName, other.packageName);
-                } else {
-                    return false;
-                }
-            }
-
-            @Override
-            public int hashCode() {
-                return packageName.hashCode() + userId;
-            }
+        Package(int userId, String packageName) {
+            this.userId = userId;
+            this.packageName = packageName;
         }
 
-        class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
-            AlarmQueue() {
-                super(1, (o1, o2) -> (int) (o1.second - o2.second));
-            }
-
-            boolean contains(@NonNull Package pkg) {
-                Pair[] alarms = toArray(new Pair[size()]);
-                for (int i = alarms.length - 1; i >= 0; --i) {
-                    if (pkg.equals(alarms[i].first)) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            /**
-             * Remove any instances of the Package from the queue.
-             *
-             * @return true if an instance was removed, false otherwise.
-             */
-            boolean remove(@NonNull Package pkg) {
-                boolean removed = false;
-                Pair[] alarms = toArray(new Pair[size()]);
-                for (int i = alarms.length - 1; i >= 0; --i) {
-                    if (pkg.equals(alarms[i].first)) {
-                        remove(alarms[i]);
-                        removed = true;
-                    }
-                }
-                return removed;
-            }
-        }
-
-        @GuardedBy("mLock")
-        private final AlarmQueue mAlarmQueue = new AlarmQueue();
-        private final String mAlarmTag;
-        /** Whether to use an exact alarm or an inexact alarm. */
-        private final boolean mExactAlarm;
-        /** The minimum amount of time between check alarms. */
-        private final long mMinTimeBetweenAlarmsMs;
-        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
-        @GuardedBy("mLock")
-        private long mTriggerTimeElapsed = 0;
-
-        protected AlarmQueueListener(@NonNull String alarmTag, boolean exactAlarm,
-                long minTimeBetweenAlarmsMs) {
-            mAlarmTag = alarmTag;
-            mExactAlarm = exactAlarm;
-            mMinTimeBetweenAlarmsMs = minTimeBetweenAlarmsMs;
-        }
-
-        @GuardedBy("mLock")
-        boolean hasAlarmScheduledLocked(int userId, @NonNull String pkgName) {
-            final Package pkg = new Package(userId, pkgName);
-            return mAlarmQueue.contains(pkg);
-        }
-
-        @GuardedBy("mLock")
-        void addAlarmLocked(int userId, @NonNull String pkgName, long alarmTimeElapsed) {
-            final Package pkg = new Package(userId, pkgName);
-            mAlarmQueue.remove(pkg);
-            mAlarmQueue.offer(new Pair<>(pkg, alarmTimeElapsed));
-            setNextAlarmLocked();
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmLocked(@NonNull Package pkg) {
-            if (mAlarmQueue.remove(pkg)) {
-                setNextAlarmLocked();
-            }
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmLocked(int userId, @NonNull String packageName) {
-            removeAlarmLocked(new Package(userId, packageName));
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmsLocked(int userId) {
-            boolean removed = false;
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                final Package pkg = (Package) alarms[i].first;
-                if (userId == pkg.userId) {
-                    mAlarmQueue.remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                setNextAlarmLocked();
-            }
-        }
-
-        /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue. */
-        @GuardedBy("mLock")
-        void setNextAlarmLocked() {
-            setNextAlarmLocked(SystemClock.elapsedRealtime());
-        }
-
-        /**
-         * Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue, using
-         * {@code earliestTriggerElapsed} as a floor.
-         */
-        @GuardedBy("mLock")
-        private void setNextAlarmLocked(long earliestTriggerElapsed) {
-            if (mAlarmQueue.size() > 0) {
-                final Pair<Package, Long> alarm = mAlarmQueue.peek();
-                final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second);
-                // Only schedule the alarm if one of the following is true:
-                // 1. There isn't one currently scheduled
-                // 2. The new alarm is significantly earlier than the previous alarm. If it's
-                // earlier but not significantly so, then we essentially delay the check for some
-                // apps by up to a minute.
-                // 3. The alarm is after the current alarm.
-                if (mTriggerTimeElapsed == 0
-                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - MINUTE_IN_MILLIS
-                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed
-                                + " for app " + alarm.first);
-                    }
-                    mHandler.post(() -> {
-                        // Never call out to AlarmManager with the lock held. This sits below AM.
-                        AlarmManager alarmManager =
-                                mIrs.getContext().getSystemService(AlarmManager.class);
-                        if (alarmManager != null) {
-                            if (mExactAlarm) {
-                                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
-                                        nextTriggerTimeElapsed, mAlarmTag, this, mHandler);
-                            } else {
-                                alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME,
-                                        nextTriggerTimeElapsed, mMinTimeBetweenAlarmsMs / 2,
-                                        mAlarmTag, this, mHandler);
-                            }
-                        } else {
-                            mHandler.sendEmptyMessageDelayed(MSG_SET_BALANCE_ALARM, 30_000);
-                        }
-                    });
-                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
-                }
-            } else {
-                mHandler.post(() -> {
-                    // Never call out to AlarmManager with the lock held. This sits below AM.
-                    AlarmManager alarmManager =
-                            mIrs.getContext().getSystemService(AlarmManager.class);
-                    if (alarmManager != null) {
-                        // This should only be null at boot time. No concerns around not
-                        // cancelling if we get null here.
-                        alarmManager.cancel(this);
-                    }
-                });
-                mTriggerTimeElapsed = 0;
-            }
-        }
-
-        @GuardedBy("mLock")
-        void dropAllAlarmsLocked() {
-            mAlarmQueue.clear();
-            setNextAlarmLocked(0);
-        }
-
-        @GuardedBy("mLock")
-        protected abstract void processExpiredAlarmLocked(int userId, @NonNull String packageName);
-
         @Override
-        public void onAlarm() {
-            synchronized (mLock) {
-                final long nowElapsed = SystemClock.elapsedRealtime();
-                while (mAlarmQueue.size() > 0) {
-                    final Pair<Package, Long> alarm = mAlarmQueue.peek();
-                    if (alarm.second <= nowElapsed) {
-                        processExpiredAlarmLocked(alarm.first.userId, alarm.first.packageName);
-                        mAlarmQueue.remove(alarm);
-                    } else {
-                        break;
-                    }
-                }
-                setNextAlarmLocked(nowElapsed + mMinTimeBetweenAlarmsMs);
-            }
+        public String toString() {
+            return appToString(userId, packageName);
         }
 
-        @GuardedBy("mLock")
-        void dumpLocked(IndentingPrintWriter pw) {
-            pw.print(mAlarmTag);
-            pw.println(" alarms:");
-            pw.increaseIndent();
-
-            if (mAlarmQueue.size() == 0) {
-                pw.println("NOT WAITING");
-            } else {
-                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-                for (int i = 0; i < alarms.length; ++i) {
-                    final Package pkg = (Package) alarms[i].first;
-                    pw.print(pkg);
-                    pw.print(": ");
-                    pw.print(alarms[i].second);
-                    pw.println();
-                }
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
             }
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof Package) {
+                Package other = (Package) obj;
+                return userId == other.userId && Objects.equals(packageName, other.packageName);
+            }
+            return false;
+        }
 
-            pw.decreaseIndent();
+        @Override
+        public int hashCode() {
+            return packageName.hashCode() + userId;
         }
     }
 
     /** Track when apps will cross the closest affordability threshold (in both directions). */
-    private class BalanceThresholdAlarmListener extends AlarmQueueListener {
-        private BalanceThresholdAlarmListener() {
-            super(ALARM_TAG_AFFORDABILITY_CHECK, true, 15_000L);
+    private class BalanceThresholdAlarmQueue extends AlarmQueue<Package> {
+        private BalanceThresholdAlarmQueue(Context context, Looper looper) {
+            super(context, looper, ALARM_TAG_AFFORDABILITY_CHECK, "Affordability check", true,
+                    15_000L);
         }
 
         @Override
-        @GuardedBy("mLock")
-        protected void processExpiredAlarmLocked(int userId, @NonNull String packageName) {
-            mHandler.obtainMessage(MSG_CHECK_BALANCE, userId, 0, packageName).sendToTarget();
+        protected boolean isForUser(@NonNull Package key, int userId) {
+            return key.userId == userId;
+        }
+
+        @Override
+        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+            for (int i = 0; i < expired.size(); ++i) {
+                Package p = expired.valueAt(i);
+                mHandler.obtainMessage(MSG_CHECK_BALANCE, p.userId, 0, p.packageName)
+                        .sendToTarget();
+            }
         }
     }
 
@@ -1288,13 +1085,6 @@
                     }
                 }
                 break;
-
-                case MSG_SET_BALANCE_ALARM: {
-                    synchronized (mLock) {
-                        mBalanceThresholdAlarmListener.setNextAlarmLocked();
-                    }
-                }
-                break;
             }
         }
     }
@@ -1302,6 +1092,6 @@
     @GuardedBy("mLock")
     void dumpLocked(IndentingPrintWriter pw) {
         pw.println();
-        mBalanceThresholdAlarmListener.dumpLocked(pw);
+        mBalanceThresholdAlarmQueue.dump(pw);
     }
 }
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
index 73f02d3..bed3895 100644
--- a/apex/media/OWNERS
+++ b/apex/media/OWNERS
@@ -8,3 +8,6 @@
 
 # go/android-fwk-media-solutions for info on areas of ownership.
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
+
+# media reliability team packages/delivers the media mainline builds.
+include platform/frameworks/av:/media/janitors/reliability_mainline_OWNERS
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index cc45589..c134822 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -52,6 +52,7 @@
         "-readability-const-return-type",
         "-readability-convert-member-functions-to-static",
         "-readability-else-after-return",
+        "-readability-identifier-length",
         "-readability-named-parameter",
         "-readability-redundant-access-specifiers",
         "-readability-uppercase-literal-suffix",
diff --git a/core/api/current.txt b/core/api/current.txt
index 7855667..bc1dc0d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1303,7 +1303,7 @@
     field public static final int shortcutLongLabel = 16844074; // 0x101052a
     field public static final int shortcutShortLabel = 16844073; // 0x1010529
     field public static final int shouldDisableView = 16843246; // 0x10101ee
-    field public static final int shouldUseDefaultDisplayStateChangeTransition;
+    field public static final int shouldUseDefaultUnfoldTransition;
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
@@ -6943,7 +6943,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
-    method public boolean shouldUseDefaultDisplayStateChangeTransition();
+    method public boolean shouldUseDefaultUnfoldTransition();
     method public boolean supportsMultipleDisplays();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -18011,6 +18011,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18705,6 +18706,12 @@
     method public android.util.Rational getElement(int, int);
   }
 
+  public final class DeviceStateOrientationMap {
+    method public int getSensorOrientation(long);
+    field public static final long FOLDED = 4L; // 0x4L
+    field public static final long NORMAL = 0L; // 0x0L
+  }
+
   public final class ExtensionSessionConfiguration {
     ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
     method @NonNull public java.util.concurrent.Executor getExecutor();
@@ -39765,6 +39772,7 @@
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
+    field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 268435456; // 0x10000000
     field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
     field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
     field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
@@ -40051,6 +40059,7 @@
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
+    field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 536870912; // 0x20000000
     field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
     field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
     field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3373d4b..5ef3d04 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2426,6 +2426,7 @@
     field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
     field public static final String ACTION_MANAGE_PERMISSIONS = "android.intent.action.MANAGE_PERMISSIONS";
     field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
+    field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_MANAGE_PERMISSION_USAGE = "android.intent.action.MANAGE_PERMISSION_USAGE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
     field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
     field public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION";
@@ -3326,14 +3327,14 @@
 
   public abstract class HdmiClient {
     method public android.hardware.hdmi.HdmiDeviceInfo getActiveSource();
-    method public void selectDevice(int, @NonNull android.hardware.hdmi.HdmiClient.SelectDeviceCallback);
+    method public void selectDevice(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiClient.OnDeviceSelectedListener);
     method public void sendKeyEvent(int, boolean);
     method public void sendVendorCommand(int, byte[], boolean);
     method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener);
   }
 
-  public static interface HdmiClient.SelectDeviceCallback {
-    method public void onComplete(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int, int);
+  public static interface HdmiClient.OnDeviceSelectedListener {
+    method public void onDeviceSelected(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int, int);
   }
 
   public final class HdmiControlManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b14b6d7..681949a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3220,13 +3220,6 @@
     method @Nullable public android.view.View getBrandingView();
   }
 
-  public final class StartingWindowInfo implements android.os.Parcelable {
-    ctor public StartingWindowInfo();
-    method public int describeContents();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.window.StartingWindowInfo> CREATOR;
-  }
-
   public final class TaskAppearedInfo implements android.os.Parcelable {
     ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
     method public int describeContents();
@@ -3293,7 +3286,6 @@
 
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
-    method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
     method @BinderThread public void copySplashScreenView(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
@@ -3306,7 +3298,6 @@
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
-    method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
     method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 59db755..19223f9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3893,7 +3893,6 @@
                         Intent intent = new Intent(activityIntent);
                         intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
-                        intent.removeUnsafeExtras();
                         content.setDefaultIntent(intent);
                     }
                 } else {
diff --git a/core/java/android/app/ILocaleManager.aidl b/core/java/android/app/ILocaleManager.aidl
new file mode 100644
index 0000000..348cb2d
--- /dev/null
+++ b/core/java/android/app/ILocaleManager.aidl
@@ -0,0 +1,43 @@
+
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.os.LocaleList;
+
+/**
+ * Internal interface used to control app-specific locales.
+ *
+ * <p>Use the {@link android.app.LocaleManager} class rather than going through
+ * this Binder interface directly. See {@link android.app.LocaleManager} for
+ * more complete documentation.
+ *
+ * @hide
+ */
+ interface ILocaleManager {
+
+     /**
+      * Sets a specified app’s app-specific UI locales.
+      */
+     void setApplicationLocales(String packageName, int userId, in LocaleList locales);
+
+     /**
+      * Returns the specified app's app-specific locales.
+      */
+     LocaleList getApplicationLocales(String packageName, int userId);
+
+ }
\ No newline at end of file
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index a969b10..99d4064 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -81,7 +81,7 @@
     final int mContextDescriptionResource;
     final boolean mShowMetadataInPreview;
     final boolean mSupportsAmbientMode;
-    final boolean mShouldUseDefaultDisplayStateChangeTransition;
+    final boolean mShouldUseDefaultUnfoldTransition;
     final String mSettingsSliceUri;
     final boolean mSupportMultipleDisplays;
 
@@ -146,9 +146,9 @@
             mSupportsAmbientMode = sa.getBoolean(
                     com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
                     false);
-            mShouldUseDefaultDisplayStateChangeTransition = sa.getBoolean(
+            mShouldUseDefaultUnfoldTransition = sa.getBoolean(
                     com.android.internal.R.styleable
-                            .Wallpaper_shouldUseDefaultDisplayStateChangeTransition, true);
+                            .Wallpaper_shouldUseDefaultUnfoldTransition, true);
             mSettingsSliceUri = sa.getString(
                     com.android.internal.R.styleable.Wallpaper_settingsSliceUri);
             mSupportMultipleDisplays = sa.getBoolean(
@@ -175,7 +175,7 @@
         mSupportsAmbientMode = source.readInt() != 0;
         mSettingsSliceUri = source.readString();
         mSupportMultipleDisplays = source.readInt() != 0;
-        mShouldUseDefaultDisplayStateChangeTransition = source.readInt() != 0;
+        mShouldUseDefaultUnfoldTransition = source.readInt() != 0;
         mService = ResolveInfo.CREATOR.createFromParcel(source);
     }
     
@@ -400,24 +400,24 @@
 
     /**
      * Returns whether this wallpaper should receive default zooming updates when the device
-     * changes its display state (e.g. when folding or unfolding a foldable device).
+     * changes its state (e.g. when folding or unfolding a foldable device).
      * If set to false the wallpaper will not receive zoom events when changing the device state,
      * so it can implement its own transition instead.
      * <p>
      * This corresponds to the value {@link
-     * android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition} in the
+     * android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition} in the
      * XML description of the wallpaper.
      * <p>
      * The default value is {@code true}.
      *
-     * @see android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition
+     * @see android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
      * @return {@code true} if wallpaper should receive default device state change
      * transition updates
      *
-     * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition
+     * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
      */
-    public boolean shouldUseDefaultDisplayStateChangeTransition() {
-        return mShouldUseDefaultDisplayStateChangeTransition;
+    public boolean shouldUseDefaultUnfoldTransition() {
+        return mShouldUseDefaultUnfoldTransition;
     }
 
     public void dump(Printer pw, String prefix) {
@@ -450,7 +450,7 @@
         dest.writeInt(mSupportsAmbientMode ? 1 : 0);
         dest.writeString(mSettingsSliceUri);
         dest.writeInt(mSupportMultipleDisplays ? 1 : 0);
-        dest.writeInt(mShouldUseDefaultDisplayStateChangeTransition ? 1 : 0);
+        dest.writeInt(mShouldUseDefaultUnfoldTransition ? 1 : 0);
         mService.writeToParcel(dest, flags);
     }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 232e220..6ea6fe0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3801,6 +3801,7 @@
             //@hide: TIME_ZONE_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
             LIGHTS_SERVICE,
+            LOCALE_SERVICE,
             //@hide: PEOPLE_SERVICE,
             //@hide: DEVICE_STATE_SERVICE,
             //@hide: SPEECH_RECOGNITION_SERVICE,
@@ -5784,6 +5785,15 @@
     public static final String DISPLAY_HASH_SERVICE = "display_hash";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.app.LocaleManager}.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String LOCALE_SERVICE = "locale";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 896bf9c..ed65af3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2306,6 +2306,47 @@
     public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES =
             "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES";
 
+    /**
+     * Activity action: Launch UI to manage the usage of a given permission group.
+     * This action would be handled by apps that want to show controls about the features
+     * which use the permission group.
+     *
+     * <p>
+     * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group for
+     * which the launched UI would be targeted.
+     * Input: {@link #EXTRA_ATTRIBUTION_TAGS} specifies the attribution tags for the usage entry.
+     * Input: {@link #EXTRA_START_TIME} specifies the start time of the period (epoch time in
+     * millis). If both start time and end time are present, start time must be <= end time.
+     * Input: {@link #EXTRA_END_TIME} specifies the end time of the period (epoch time in
+     * millis). If the end time is empty, that implies that the permission usage is still in use.
+     * If both start time and end time are present, start time must be <= end time.
+     * Input: {@link #EXTRA_SHOWING_ATTRIBUTION} specifies whether the subattribution was shown
+     * in the UI.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     * <p class="note">
+     * You must protect the activity that handles this action with the
+     * {@link android.Manifest.permission#START_VIEW_PERMISSION_USAGE} permission to ensure that
+     * only the system can launch this activity. The system will not launch activities
+     * that are not properly protected.
+     * </p>
+     *
+     * @see #EXTRA_PERMISSION_GROUP_NAME
+     * @see #EXTRA_ATTRIBUTION_TAGS
+     * @see #EXTRA_START_TIME
+     * @see #EXTRA_END_TIME
+     * @see #EXTRA_SHOWING_ATTRIBUTION
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_PERMISSION_USAGE =
+            "android.intent.action.MANAGE_PERMISSION_USAGE";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent broadcast actions (see action variable).
@@ -8971,16 +9012,6 @@
     }
 
     /**
-     * Filter extras to only basic types.
-     * @hide
-     */
-    public void removeUnsafeExtras() {
-        if (mExtras != null) {
-            mExtras = mExtras.filterValues();
-        }
-    }
-
-    /**
      * @return Whether {@link #maybeStripForHistory} will return an lightened intent or
      * return itself as-is.
      * @hide
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e0138c5..ddac22c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,15 +22,18 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.DeviceStateOrientationMap;
 import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.utils.TypeReference;
 import android.os.Build;
+import android.util.Log;
 import android.util.Rational;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -202,8 +205,25 @@
     private List<CaptureResult.Key<?>> mAvailableResultKeys;
     private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations;
 
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mFoldedDeviceState;
+
+    private final CameraManager.DeviceStateListener mFoldStateListener =
+            new CameraManager.DeviceStateListener() {
+                @Override
+                public final void onDeviceStateChanged(boolean folded) {
+                    synchronized (mLock) {
+                        mFoldedDeviceState = folded;
+                    }
+                }};
+
+    private static final String TAG = "CameraCharacteristics";
+
     /**
      * Takes ownership of the passed-in properties object
+     *
+     * @param properties Camera properties.
      * @hide
      */
     public CameraCharacteristics(CameraMetadataNative properties) {
@@ -220,6 +240,41 @@
     }
 
     /**
+     * Return the device state listener for this Camera characteristics instance
+     */
+    CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+
+    /**
+     * Overrides the property value
+     *
+     * <p>Check whether a given property value needs to be overridden in some specific
+     * case.</p>
+     *
+     * @param key The characteristics field to override.
+     * @return The value of overridden property, or {@code null} if the property doesn't need an
+     * override.
+     */
+    @Nullable
+    private <T> T overrideProperty(Key<T> key) {
+        if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
+                (mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
+            DeviceStateOrientationMap deviceStateOrientationMap =
+                    mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
+            synchronized (mLock) {
+                Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
+                        DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
+                if (ret >= 0) {
+                    return (T) ret;
+                } else {
+                    Log.w(TAG, "No valid device state to orientation mapping! Using default!");
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Get a camera characteristics field value.
      *
      * <p>The field definitions can be
@@ -235,7 +290,8 @@
      */
     @Nullable
     public <T> T get(Key<T> key) {
-        return mProperties.get(key);
+        T propertyOverride = overrideProperty(key);
+        return (propertyOverride != null) ? propertyOverride : mProperties.get(key);
     }
 
     /**
@@ -3993,11 +4049,26 @@
      * upright on the device screen in its native orientation.</p>
      * <p>Also defines the direction of rolling shutter readout, which is from top to bottom in
      * the sensor's coordinate system.</p>
+     * <p>Starting with Android API level 32, camera clients that query the orientation via
+     * {@link android.hardware.camera2.CameraCharacteristics#get } on foldable devices which
+     * include logical cameras can receive a value that can dynamically change depending on the
+     * device/fold state.
+     * Clients are advised to not cache or store the orientation value of such logical sensors.
+     * In case repeated queries to CameraCharacteristics are not preferred, then clients can
+     * also access the entire mapping from device state to sensor orientation in
+     * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+     * Do note that a dynamically changing sensor orientation value in camera characteristics
+     * will not be the best way to establish the orientation per frame. Clients that want to
+     * know the sensor orientation of a particular captured frame should query the
+     * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} from the corresponding capture result and
+     * check the respective physical camera orientation.</p>
      * <p><b>Units</b>: Degrees of clockwise rotation; always a multiple of
      * 90</p>
      * <p><b>Range of valid values:</b><br>
      * 0, 90, 180, 270</p>
      * <p>This key is available on all devices.</p>
+     *
+     * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
      */
     @PublicKey
     @NonNull
@@ -4307,6 +4378,46 @@
             new Key<String>("android.info.version", String.class);
 
     /**
+     * <p>This lists the mapping between a device folding state and
+     * specific camera sensor orientation for logical cameras on a foldable device.</p>
+     * <p>Logical cameras on foldable devices can support sensors with different orientation
+     * values. The orientation value may need to change depending on the specific folding
+     * state. Information about the mapping between the device folding state and the
+     * sensor orientation can be obtained in
+     * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+     * Device state orientation maps are optional and maybe present on devices that support
+     * {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @see CaptureRequest#SCALER_ROTATE_AND_CROP
+     */
+    @PublicKey
+    @NonNull
+    @SyntheticKey
+    public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
+            new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);
+
+    /**
+     * <p>HAL must populate the array with
+     * (hardware::camera::provider::V2_5::DeviceState, sensorOrientation) pairs for each
+     * supported device state bitwise combination.</p>
+     * <p><b>Units</b>: (device fold state, sensor orientation) x n</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * <p><b>Limited capability</b> -
+     * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+     * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+     *
+     * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+     * @hide
+     */
+    public static final Key<long[]> INFO_DEVICE_STATE_ORIENTATIONS =
+            new Key<long[]>("android.info.deviceStateOrientations", long[].class);
+
+    /**
      * <p>The maximum number of frames that can occur after a request
      * (different than the previous) has been submitted, and before the
      * result's state becomes synchronized.</p>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index f5306b8..2e86a8b 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,15 +31,19 @@
 import android.hardware.camera2.impl.CameraDeviceImpl;
 import android.hardware.camera2.impl.CameraInjectionSessionImpl;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.params.DeviceStateOrientationMap;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfiguration;
 import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
 import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.DeadObjectException;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -51,6 +55,10 @@
 import android.util.Size;
 import android.view.Display;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -105,6 +113,80 @@
                     mContext.checkSelfPermission(CAMERA_OPEN_CLOSE_LISTENER_PERMISSION) ==
                     PackageManager.PERMISSION_GRANTED;
         }
+
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mFoldStateListener = new FoldStateListener(context);
+        context.getSystemService(DeviceStateManager.class)
+                .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+    }
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private FoldStateListener mFoldStateListener;
+    @GuardedBy("mLock")
+    private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = new ArrayList<>();
+    private boolean mFoldedDeviceState;
+
+    /**
+     * @hide
+     */
+    public interface DeviceStateListener {
+        void onDeviceStateChanged(boolean folded);
+    }
+
+    private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+        private final int[] mFoldedDeviceStates;
+
+        public FoldStateListener(Context context) {
+            mFoldedDeviceStates = context.getResources().getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates);
+        }
+
+        private void handleStateChange(int state) {
+            boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+            synchronized (mLock) {
+                mFoldedDeviceState = folded;
+                ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
+                for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
+                    DeviceStateListener callback = listener.get();
+                    if (callback != null) {
+                        callback.onDeviceStateChanged(folded);
+                    } else {
+                        invalidListeners.add(listener);
+                    }
+                }
+                if (!invalidListeners.isEmpty()) {
+                    mDeviceStateListeners.removeAll(invalidListeners);
+                }
+            }
+        }
+
+        @Override
+        public final void onBaseStateChanged(int state) {
+            handleStateChange(state);
+        }
+
+        @Override
+        public final void onStateChanged(int state) {
+            handleStateChange(state);
+        }
+    }
+
+    /**
+     * Register a {@link CameraCharacteristics} device state listener
+     *
+     * @param chars Camera characteristics that need to receive device state updates
+     *
+     * @hide
+     */
+    public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) {
+        synchronized (mLock) {
+            DeviceStateListener listener = chars.getDeviceStateListener();
+            listener.onDeviceStateChanged(mFoldedDeviceState);
+            mDeviceStateListeners.add(new WeakReference<>(listener));
+        }
     }
 
     /**
@@ -513,6 +595,7 @@
                         "Camera service is currently unavailable", e);
             }
         }
+        registerDeviceStateListener(characteristics);
         return characteristics;
     }
 
@@ -1339,8 +1422,7 @@
         private boolean mHasOpenCloseListenerPermission = false;
 
         // Singleton, don't allow construction
-        private CameraManagerGlobal() {
-        }
+        private CameraManagerGlobal() { }
 
         public static final boolean sCameraServiceDisabled =
                 SystemProperties.getBoolean("config.disable_cameraservice", false);
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 196134b..3745022 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,6 +50,7 @@
 import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
 import android.hardware.camera2.marshal.impl.MarshalQueryableString;
 import android.hardware.camera2.params.Capability;
+import android.hardware.camera2.params.DeviceStateOrientationMap;
 import android.hardware.camera2.params.Face;
 import android.hardware.camera2.params.HighSpeedVideoConfiguration;
 import android.hardware.camera2.params.LensShadingMap;
@@ -754,7 +755,7 @@
                 });
         sGetCommandMap.put(
                 CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP.getNativeKey(),
-                        new GetCommand() {
+                new GetCommand() {
                     @Override
                     @SuppressWarnings("unchecked")
                     public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
@@ -762,6 +763,15 @@
                     }
                 });
         sGetCommandMap.put(
+                CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
+                        new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getDeviceStateOrientationMap();
+                    }
+                });
+        sGetCommandMap.put(
                 CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(),
                         new GetCommand() {
                     @Override
@@ -994,6 +1004,18 @@
         return map;
     }
 
+    private DeviceStateOrientationMap getDeviceStateOrientationMap() {
+        long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
+
+        // Do not warn if map is null while s is not. This is valid.
+        if (mapArray == null) {
+            return null;
+        }
+
+        DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
+        return map;
+    }
+
     private Location getGpsLocation() {
         String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
         double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
diff --git a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
new file mode 100644
index 0000000..3907f04
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.params;
+
+import android.annotation.LongDef;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.utils.HashCodeHelpers;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * Immutable class that maps the device fold state to sensor orientation.
+ *
+ * <p>Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical}
+ * cameras on foldables can include physical sensors with different sensor orientation
+ * values. As a result, the values of the logical camera device can potentially change depending
+ * on the device fold state.</p>
+ *
+ * <p>The device fold state to sensor orientation map will contain information about the
+ * respective logical camera sensor orientation given a device state. Clients
+ * can query the mapping for all possible supported folded states.
+ *
+ * @see CameraCharacteristics#SENSOR_ORIENTATION
+ */
+public final class DeviceStateOrientationMap {
+    /**
+     *  Needs to be kept in sync with the HIDL/AIDL DeviceState
+     */
+
+    /**
+     * The device is in its normal physical configuration. This is the default if the
+     * device does not support multiple different states.
+     */
+    public static final long NORMAL = 0;
+
+    /**
+     * The device is folded.  If not set, the device is unfolded or does not
+     * support folding.
+     *
+     * The exact point when this status change happens during the folding
+     * operation is device-specific.
+     */
+    public static final long FOLDED = 1 << 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(prefix = {"DEVICE_STATE"}, value =
+            {NORMAL,
+             FOLDED })
+    public @interface DeviceState {};
+
+    private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();
+
+    /**
+     * Create a new immutable DeviceStateOrientationMap instance.
+     *
+     * <p>This constructor takes over the array; do not write to the array afterwards.</p>
+     *
+     * @param elements
+     *          An array of elements describing the map
+     *
+     * @throws IllegalArgumentException
+     *            if the {@code elements} array length is invalid, not divisible by 2 or contains
+     *            invalid element values
+     * @throws NullPointerException
+     *            if {@code elements} is {@code null}
+     *
+     * @hide
+     */
+    public DeviceStateOrientationMap(final long[] elements) {
+        mElements = Objects.requireNonNull(elements, "elements must not be null");
+        if ((elements.length % 2) != 0) {
+            throw new IllegalArgumentException("Device state orientation map length " +
+                    elements.length + " is not even!");
+        }
+
+        for (int i = 0; i < elements.length; i += 2) {
+            if ((elements[i+1] % 90) != 0) {
+                throw new IllegalArgumentException("Sensor orientation not divisible by 90: " +
+                        elements[i+1]);
+            }
+
+            mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1]));
+        }
+    }
+
+    /**
+     * Return the logical camera sensor orientation given a specific device fold state.
+     *
+     * @param deviceState Device fold state
+     *
+     * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for
+     *         any supported device fold state
+     *
+     * @throws IllegalArgumentException if the given device state is invalid
+     */
+    public int getSensorOrientation(@DeviceState long deviceState) {
+        if (!mDeviceStateOrientationMap.containsKey(deviceState)) {
+            throw new IllegalArgumentException("Invalid device state: " + deviceState);
+        }
+
+        return mDeviceStateOrientationMap.get(deviceState);
+    }
+
+    /**
+     * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
+     *
+     * <p>Two device state orientation maps are equal if and only if all of their elements are
+     * {@link Object#equals equal}.</p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DeviceStateOrientationMap) {
+            final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
+            return Arrays.equals(mElements, other.mElements);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return HashCodeHelpers.hashCodeGeneric(mElements);
+    }
+
+    private final long[] mElements;
+}
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index c94a507..1a52f11 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -1,12 +1,16 @@
 package android.hardware.hdmi;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.hardware.hdmi.HdmiControlManager.VendorCommandListener;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.concurrent.Executor;
+
 /**
  * Parent for classes of various HDMI-CEC device type used to access
  * the HDMI control system service. Contains methods and data used in common.
@@ -28,16 +32,17 @@
     }
 
     /**
-     * Callback interface used to get the result of {@link #selectDevice}.
+     * Listener interface used to get the result of {@link #selectDevice}.
      */
-    public interface SelectDeviceCallback {
+    public interface OnDeviceSelectedListener {
         /**
          * Called when the operation is finished.
          * @param result the result value of {@link #selectDevice} and can have the values mentioned
          *               in {@link HdmiControlShellCommand#getResultString}
          * @param logicalAddress logical address of the selected device
          */
-        void onComplete(@HdmiControlManager.ControlCallbackResult int result, int logicalAddress);
+        void onDeviceSelected(@HdmiControlManager.ControlCallbackResult int result,
+                int logicalAddress);
     }
 
     /**
@@ -48,15 +53,19 @@
      * containing the result of that call only.
      *
      * @param logicalAddress logical address of the device to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @param listener listener to get the result with
+     * @throws {@link IllegalArgumentException} if the {@code listener} is null
      */
-    public void selectDevice(int logicalAddress, @NonNull SelectDeviceCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null.");
+    public void selectDevice(
+            int logicalAddress,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnDeviceSelectedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null.");
         }
         try {
-            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback, logicalAddress));
+            mService.deviceSelect(logicalAddress,
+                    getCallbackWrapper(logicalAddress, executor, listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select device: ", e);
         }
@@ -65,12 +74,14 @@
     /**
      * @hide
      */
-    private static IHdmiControlCallback getCallbackWrapper(final SelectDeviceCallback callback,
-            int logicalAddress) {
+    private static IHdmiControlCallback getCallbackWrapper(int logicalAddress,
+            final Executor executor, final OnDeviceSelectedListener listener) {
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
-                callback.onComplete(result, logicalAddress);
+                Binder.withCleanCallingIdentity(
+                        () -> executor.execute(() -> listener.onDeviceSelected(result,
+                                logicalAddress)));
             }
         };
     }
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 79846cd..0fb5894 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -85,7 +85,6 @@
      * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()}
      *     to get portId of a specific TV Input.
      * @param listener listener to get the result with
-     *
      * @hide
      */
     @SystemApi
@@ -104,8 +103,10 @@
      *
      * @param logicalAddress logical address of the device to select
      * @param listener       listener to get the result with
+     * @deprecated Please use {@link HdmiClient#selectDevice} instead.
      * @hide
      */
+    @Deprecated
     public void selectDevice(
             int logicalAddress,
             @NonNull @CallbackExecutor Executor executor,
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 7ce8d72..2a344f6 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -43,18 +43,22 @@
     protected static final String TAG = "Bundle";
     static final boolean DEBUG = false;
 
-    // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
-    private static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
+    /**
+     * Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
     private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N'
 
     /**
-     * Flag indicating that this Bundle is okay to "defuse." That is, it's okay
-     * for system processes to ignore any {@link BadParcelableException}
-     * encountered when unparceling it, leaving an empty bundle in its place.
+     * Flag indicating that this Bundle is okay to "defuse", see {@link #setShouldDefuse(boolean)}
+     * for more details.
      * <p>
-     * This should <em>only</em> be set when the Bundle reaches its final
-     * destination, otherwise a system process may clobber contents that were
-     * destined for an app that could have unparceled them.
+     * This should <em>only</em> be set when the Bundle reaches its final destination, otherwise a
+     * system process may clobber contents that were destined for an app that could have unparceled
+     * them.
      */
     static final int FLAG_DEFUSABLE = 1 << 0;
 
@@ -63,10 +67,15 @@
     private static volatile boolean sShouldDefuse = false;
 
     /**
-     * Set global variable indicating that any Bundles parsed in this process
-     * should be "defused." That is, any {@link BadParcelableException}
-     * encountered will be suppressed and logged, leaving an empty Bundle
-     * instead of crashing.
+     * Set global variable indicating that any Bundles parsed in this process should be "defused".
+     * That is, any {@link BadParcelableException} encountered will be suppressed and logged. Also:
+     * <ul>
+     *   <li>If it was the deserialization of a custom item (eg. {@link Parcelable}) that caused the
+     *   exception, {@code null} will be returned but the item will be held in the map in its
+     *   serialized form (lazy value).
+     *   <li>If the exception happened during partial deserialization, that is, during the read of
+     *   the map and its basic types (while skipping custom types), the map will be left empty.
+     * </ul>
      *
      * @hide
      */
@@ -249,6 +258,12 @@
                 }
             }
             if (itemwise) {
+                if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
+                    Slog.wtf(TAG,
+                            "Attempting to unparcel all items in a Bundle while in transit; this "
+                                    + "may remove elements intended for the final desitination.",
+                            new Throwable());
+                }
                 for (int i = 0, n = mMap.size(); i < n; i++) {
                     // Triggers deserialization of i-th item, if needed
                     getValueAt(i);
@@ -281,7 +296,16 @@
     final Object getValueAt(int i) {
         Object object = mMap.valueAt(i);
         if (object instanceof Supplier<?>) {
-            object = ((Supplier<?>) object).get();
+            try {
+                object = ((Supplier<?>) object).get();
+            } catch (BadParcelableException e) {
+                if (sShouldDefuse) {
+                    Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
+                    return null;
+                } else {
+                    throw e;
+                }
+            }
             mMap.setValueAt(i, object);
         }
         return object;
@@ -289,11 +313,6 @@
 
     private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
             boolean parcelledByNative) {
-        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
-            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
-                    + "clobber all data inside!", new Throwable());
-        }
-
         if (isEmptyParcel(parcelledData)) {
             if (DEBUG) {
                 Log.d(TAG, "unparcel "
@@ -376,8 +395,16 @@
         }
     }
 
-    /** @hide */
-    ArrayMap<String, Object> getMap() {
+    /**
+     * Returns the backing map of this bundle after deserializing every item.
+     *
+     * <p><b>Warning:</b> This method will deserialize every item on the bundle, including custom
+     * types such as {@link Parcelable} and {@link Serializable}, so only use this when you trust
+     * the source. Specifically don't use this method on app-provided bundles.
+     *
+     * @hide
+     */
+    ArrayMap<String, Object> getItemwiseMap() {
         unparcel(/* itemwise */ true);
         return mMap;
     }
@@ -500,7 +527,7 @@
                     final int N = fromMap.size();
                     mMap = new ArrayMap<>(N);
                     for (int i = 0; i < N; i++) {
-                        mMap.append(fromMap.keyAt(i), deepCopyValue(from.getValueAt(i)));
+                        mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
                     }
                 }
             } else {
@@ -1772,7 +1799,7 @@
             pw.println("[null]");
             return;
         }
-        final ArrayMap<String, Object> map = bundle.getMap();
+        final ArrayMap<String, Object> map = bundle.getItemwiseMap();
         for (int i = 0; i < map.size(); i++) {
             dumpStats(pw, map.keyAt(i), map.valueAt(i));
         }
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index b6163f1..92eb7a5 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -33,6 +33,9 @@
 /**
  * A mapping from String keys to various {@link Parcelable} values.
  *
+ * <p><b>Warning:</b> Note that {@link Bundle} is a lazy container and as such it does NOT implement
+ * {@link #equals(Object)} or {@link #hashCode()}.
+ *
  * @see PersistableBundle
  */
 public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@@ -348,56 +351,6 @@
         return (mFlags & FLAG_HAS_FDS) != 0;
     }
 
-    /**
-     * Filter values in Bundle to only basic types.
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public Bundle filterValues() {
-        unparcel(/* itemwise */ true);
-        Bundle bundle = this;
-        if (mMap != null) {
-            ArrayMap<String, Object> map = mMap;
-            for (int i = map.size() - 1; i >= 0; i--) {
-                Object value = map.valueAt(i);
-                if (PersistableBundle.isValidType(value)) {
-                    continue;
-                }
-                if (value instanceof Bundle) {
-                    Bundle newBundle = ((Bundle)value).filterValues();
-                    if (newBundle != value) {
-                        if (map == mMap) {
-                            // The filter had to generate a new bundle, but we have not yet
-                            // created a new one here.  Do that now.
-                            bundle = new Bundle(this);
-                            // Note the ArrayMap<> constructor is guaranteed to generate
-                            // a new object with items in the same order as the original.
-                            map = bundle.mMap;
-                        }
-                        // Replace this current entry with the new child bundle.
-                        map.setValueAt(i, newBundle);
-                    }
-                    continue;
-                }
-                if (value.getClass().getName().startsWith("android.")) {
-                    continue;
-                }
-                if (map == mMap) {
-                    // This is the first time we have had to remove something, that means we
-                    // need to switch to a new Bundle.
-                    bundle = new Bundle(this);
-                    // Note the ArrayMap<> constructor is guaranteed to generate
-                    // a new object with items in the same order as the original.
-                    map = bundle.mMap;
-                }
-                map.removeAt(i);
-            }
-        }
-        mFlags |= FLAG_HAS_FDS_KNOWN;
-        mFlags &= ~FLAG_HAS_FDS;
-        return bundle;
-    }
-
     /** {@hide} */
     @Override
     public void putObject(@Nullable String key, @Nullable Object value) {
@@ -1280,6 +1233,10 @@
         maybePrefillHasFds();
     }
 
+    /**
+     * Returns a string representation of the {@link Bundle} that may be suitable for debugging. It
+     * won't print the internal map if its content hasn't been unparcelled.
+     */
     @Override
     public synchronized String toString() {
         if (mParcelledData != null) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 5efa390..44d51db 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3573,14 +3573,17 @@
             Parcel source = mSource;
             if (source != null) {
                 synchronized (source) {
-                    int restore = source.dataPosition();
-                    try {
-                        source.setDataPosition(mPosition);
-                        mObject = source.readValue(mLoader);
-                    } finally {
-                        source.setDataPosition(restore);
+                    // Check mSource != null guarantees callers won't ever see different objects.
+                    if (mSource != null) {
+                        int restore = source.dataPosition();
+                        try {
+                            source.setDataPosition(mPosition);
+                            mObject = source.readValue(mLoader);
+                        } finally {
+                            source.setDataPosition(restore);
+                        }
+                        mSource = null;
                     }
-                    mSource = null;
                 }
             }
             return mObject;
@@ -3817,7 +3820,7 @@
 
             default:
                 int off = dataPosition() - 4;
-                throw new RuntimeException(
+                throw new BadParcelableException(
                     "Parcel " + this + ": Unmarshalling unknown type code " + type
                             + " at offset " + off);
         }
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index cad6e66..7b55e710 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -35,6 +35,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.Serializable;
 import java.util.ArrayList;
 
 /**
@@ -42,6 +43,9 @@
  * supported by this class is purposefully restricted to simple objects that can
  * safely be persisted to and restored from disk.
  *
+ * <p><b>Warning:</b> Note that {@link PersistableBundle} is a lazy container and as such it does
+ * NOT implement {@link #equals(Object)} or {@link #hashCode()}.
+ *
  * @see Bundle
  */
 public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
@@ -103,6 +107,10 @@
     /**
      * Constructs a PersistableBundle from a Bundle.  Does only a shallow copy of the Bundle.
      *
+     * <p><b>Warning:</b> This method will deserialize every item on the bundle, including custom
+     * types such as {@link Parcelable} and {@link Serializable}, so only use this when you trust
+     * the source. Specifically don't use this method on app-provided bundles.
+     *
      * @param b a Bundle to be copied.
      *
      * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
@@ -110,7 +118,7 @@
      * @hide
      */
     public PersistableBundle(Bundle b) {
-        this(b.getMap());
+        this(b.getItemwiseMap());
     }
 
     /**
@@ -319,8 +327,12 @@
         return new PersistableBundle();  // An empty mutable PersistableBundle
     }
 
+    /**
+     * Returns a string representation of the {@link PersistableBundle} that may be suitable for
+     * debugging. It won't print the internal map if its content hasn't been unparcelled.
+     */
     @Override
-    synchronized public String toString() {
+    public synchronized String toString() {
         if (mParcelledData != null) {
             if (isEmptyParcel()) {
                 return "PersistableBundle[EMPTY_PARCEL]";
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index da09375..a16d2ba 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1848,7 +1848,7 @@
             Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
             conditional = true)
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @UserHandleAware()
+    @UserHandleAware
     public boolean canSwitchUsers() {
         boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
                 mContext.getContentResolver(),
@@ -1929,8 +1929,13 @@
      * Returns the userId for the context user.
      *
      * @return the userId of the context user.
+     *
+     * @deprecated To get the <em>calling</em> user, use {@link UserHandle#myUserId()}.
+     *             To get the <em>context</em> user, get it directly from the context.
+     *
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     // *** Do NOT use this in UserManager. Instead always use mUserId. ***
@@ -1939,6 +1944,21 @@
     }
 
     /**
+     * Returns the userId for the user that this process is running under
+     * (<em>not</em> the context user).
+     *
+     * @return the userId of <em>this process</em>.
+     *
+     * @deprecated Use {@link UserHandle#myUserId()}
+     * @hide
+     */
+    @Deprecated
+    // NOT @UserHandleAware
+    public @UserIdInt int getProcessUserId() {
+        return UserHandle.myUserId();
+    }
+
+    /**
      * @return the user type of the context user.
      * @hide
      */
@@ -2012,7 +2032,7 @@
             return false;
         }
         // Caution: This is NOT @UserHandleAware (because mContext is getApplicationContext and
-        // holds a different userId), but for R+ it returns false, so it doesn't matter anyway.
+        // can hold a different userId), but for R+ it returns false, so it doesn't matter anyway.
         return mContext.getPackageManager()
                 .isPackageAvailable("com.coffeestainstudios.goatsimulator");
     }
@@ -2447,7 +2467,7 @@
      */
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS})
-    @UserHandleAware()
+    @UserHandleAware
     public boolean isEphemeralUser() {
         return isUserEphemeral(mUserId);
     }
@@ -3315,7 +3335,7 @@
     @TestApi
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS})
-    @UserHandleAware()
+    @UserHandleAware
     public @Nullable UserInfo createRestrictedProfile(@Nullable String name) {
         try {
             final int parentUserId = mUserId;
@@ -4605,7 +4625,7 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS // And INTERACT_ if diff profile group
     })
-    @UserHandleAware()
+    @UserHandleAware
     public boolean isUserSwitcherEnabled() {
         return isUserSwitcherEnabled(true);
     }
@@ -4622,7 +4642,7 @@
             android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.CREATE_USERS // And INTERACT_ if diff profile group
     })
-    @UserHandleAware()
+    @UserHandleAware
     public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
         if (!supportsMultipleUsers()) {
             return false;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 51f19eb..8553c24 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -1742,7 +1742,7 @@
             Uri result = null;
 
             final UserManager userManager = context.getSystemService(UserManager.class);
-            final int currentUserId = userManager.getUserHandle();
+            final int currentUserId = userManager.getProcessUserId();
 
             if (params.mAddForAllUsers) {
                 if (userManager.isUserUnlocked(UserHandle.SYSTEM)) {
diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java
index 5d34669..de895d9 100644
--- a/core/java/android/view/AccessibilityEmbeddedConnection.java
+++ b/core/java/android/view/AccessibilityEmbeddedConnection.java
@@ -63,7 +63,6 @@
                     viewRootImpl.mContext);
             viewRootImpl.mAttachInfo.mLeashedParentToken = null;
             viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID;
-            viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0);
             if (accessibilityManager.isEnabled()) {
                 accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken);
             }
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 656fdbd..9cd8313 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -878,21 +878,6 @@
         return false;
     }
 
-    private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
-        if (info == null || shouldBypassAdjustBoundsInScreen()) {
-            return;
-        }
-        final Rect boundsInScreen = mTempRect;
-        info.getBoundsInScreen(boundsInScreen);
-        boundsInScreen.offset(mViewRootImpl.mAttachInfo.mLocationInParentDisplay.x,
-                mViewRootImpl.mAttachInfo.mLocationInParentDisplay.y);
-        info.setBoundsInScreen(boundsInScreen);
-    }
-
-    private boolean shouldBypassAdjustBoundsInScreen() {
-        return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
-    }
-
     private void applyScreenMatrixIfNeeded(List<AccessibilityNodeInfo> infos) {
         if (infos == null || shouldBypassApplyScreenMatrix()) {
             return;
@@ -1009,7 +994,6 @@
                                        Region interactiveRegion) {
         associateLeashedParentIfNeeded(info);
         applyScreenMatrixIfNeeded(info);
-        adjustBoundsInScreenIfNeeded(info);
         // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
         // then impact the visibility result, we need to adjust visibility before apply scale.
         adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index b8b13b9..2ee112b 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -58,12 +58,6 @@
             boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId);
 
     /**
-     * Called when the window location in parent display has changed. The offset will only be a
-     * nonzero value if the window is on an embedded display that is re-parented to another window.
-     */
-    void locationInParentDisplayChanged(in Point offset);
-
-    /**
      * Called when the window insets configuration has changed.
      *
      * @param willMove The window frame will be moved soon.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index ae2eed8..f7d32d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -548,16 +548,6 @@
     boolean isWindowTraceEnabled();
 
     /**
-     * Notify WindowManager that it should not override the info in DisplayManager for the specified
-     * display. This can disable letter- or pillar-boxing applied in DisplayManager when the metrics
-     * of the logical display reported from WindowManager do not correspond to the metrics of the
-     * physical display it is based on.
-     *
-     * @param displayId The id of the display.
-     */
-    void dontOverrideDisplayInfo(int displayId);
-
-    /**
      * Gets the windowing mode of the display.
      *
      * @param displayId The id of the display.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 9da5088..921ce53 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -273,17 +273,6 @@
     oneway void updatePointerIcon(IWindow window);
 
     /**
-     * Update the location of a child display in its parent window. This enables windows in the
-     * child display to compute the global transformation matrix.
-     *
-     * @param window The parent window of the display.
-     * @param x The x coordinate in the parent window.
-     * @param y The y coordinate in the parent window.
-     * @param displayId The id of the display to be notified.
-     */
-    oneway void updateDisplayContentLocation(IWindow window, int x, int y, int displayId);
-
-    /**
      * Update a tap exclude region identified by provided id in the window. Touches on this region
      * will neither be dispatched to this window nor change the focus to this window. Passing an
      * invalid region will remove the area from the exclude region of this window.
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index 99141c3..278b2fc 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -24,9 +24,9 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
-import android.os.Trace;
 import android.util.CloseGuard;
 import android.util.Log;
 
@@ -44,7 +44,8 @@
  *
  * @hide
  */
-public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
+public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub implements
+        IBinder.DeathRecipient {
 
     private static final String TAG = "ScrollCaptureConnection";
 
@@ -54,15 +55,13 @@
     private final Executor mUiThread;
     private final CloseGuard mCloseGuard = new CloseGuard();
 
-
     private ScrollCaptureCallback mLocal;
     private IScrollCaptureCallbacks mRemote;
-
     private ScrollCaptureSession mSession;
-
     private CancellationSignal mCancellation;
 
     private volatile boolean mActive;
+    private volatile boolean mConnected;
 
     /**
      * Constructs a ScrollCaptureConnection.
@@ -87,13 +86,14 @@
     @Override
     public ICancellationSignal startCapture(@NonNull Surface surface,
             @NonNull IScrollCaptureCallbacks remote) throws RemoteException {
-
         mCloseGuard.open("close");
 
         if (!surface.isValid()) {
             throw new RemoteException(new IllegalArgumentException("surface must be valid"));
         }
         mRemote = requireNonNull(remote, "<callbacks> must non-null");
+        mRemote.asBinder().linkToDeath(this, 0);
+        mConnected = true;
 
         ICancellationSignal cancellation = CancellationSignal.createTransport();
         mCancellation = CancellationSignal.fromTransport(cancellation);
@@ -115,14 +115,14 @@
             Log.w(TAG, "Shutting down due to error: ", e);
             close();
         }
+        mCancellation = null;
     }
 
     @BinderThread
     @Override
     public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
-        Trace.beginSection("requestImage");
         checkActive();
-
+        cancelPendingAction();
         ICancellationSignal cancellation = CancellationSignal.createTransport();
         mCancellation = CancellationSignal.fromTransport(cancellation);
 
@@ -131,7 +131,6 @@
         // -> UiThread
         mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest(
                 mSession, mCancellation, new Rect(requestRect), listener));
-        Trace.endSection();
         return cancellation;
     }
 
@@ -142,6 +141,8 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Shutting down due to error: ", e);
             close();
+        } finally {
+            mCancellation = null;
         }
     }
 
@@ -149,7 +150,7 @@
     @Override
     public ICancellationSignal endCapture() throws RemoteException {
         checkActive();
-
+        cancelPendingAction();
         ICancellationSignal cancellation = CancellationSignal.createTransport();
         mCancellation = CancellationSignal.fromTransport(cancellation);
 
@@ -170,26 +171,32 @@
         } catch (RemoteException e) {
             Log.w(TAG, "Caught exception confirming capture end!", e);
         } finally {
+            mCancellation = null;
             close();
         }
     }
 
+    @Override
+    public void binderDied() {
+        Log.e(TAG, "Controlling process just died.");
+        close();
+    }
+
     @BinderThread
     @Override
     public void close() {
         if (mActive) {
-            if (mCancellation != null) {
-                Log.w(TAG, "close(): cancelling pending operation.");
-                mCancellation.cancel();
-                mCancellation = null;
-            }
             Log.w(TAG, "close(): capture session still active! Ending now.");
-            // -> UiThread
+            cancelPendingAction();
             final ScrollCaptureCallback callback = mLocal;
             mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
             mActive = false;
         }
+        if (mRemote != null) {
+            mRemote.asBinder().unlinkToDeath(this, 0);
+        }
         mActive = false;
+        mConnected = false;
         mSession = null;
         mRemote = null;
         mLocal = null;
@@ -197,6 +204,19 @@
         Reference.reachabilityFence(this);
     }
 
+    private void cancelPendingAction() {
+        if (mCancellation != null) {
+            Log.w(TAG, "cancelling pending operation.");
+            mCancellation.cancel();
+            mCancellation = null;
+        }
+    }
+
+    @VisibleForTesting
+    public boolean isConnected() {
+        return mConnected;
+    }
+
     @VisibleForTesting
     public boolean isActive() {
         return mActive;
@@ -236,7 +256,7 @@
 
         protected SafeCallback(CancellationSignal signal, Executor executor, T value) {
             mSignal = signal;
-            mValue = new AtomicReference<T>(value);
+            mValue = new AtomicReference<>(value);
             mExecutor = executor;
         }
 
@@ -257,7 +277,7 @@
 
         static <T> Consumer<T> create(CancellationSignal signal, Executor executor,
                 Consumer<T> target) {
-            return new ConsumerCallback<T>(signal, executor, target);
+            return new ConsumerCallback<>(signal, executor, target);
         }
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6420236..bbdbb9b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -29520,12 +29520,6 @@
         boolean mHandlingPointerEvent;
 
         /**
-         * The offset of this view's window when it's on an embedded display that is re-parented
-         * to another window.
-         */
-        final Point mLocationInParentDisplay = new Point();
-
-        /**
          * The screen matrix of this view when it's on a {@link SurfaceControlViewHost} that is
          * embedded within a SurfaceView.
          */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9af0a02..15d5555 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4685,13 +4685,6 @@
         }
     }
 
-    void updateLocationInParentDisplay(int x, int y) {
-        if (mAttachInfo != null
-                && !mAttachInfo.mLocationInParentDisplay.equals(x, y)) {
-            mAttachInfo.mLocationInParentDisplay.set(x, y);
-        }
-    }
-
     /**
      * Set the root-level system gesture exclusion rects. These are added to those provided by
      * the root's view hierarchy.
@@ -5203,7 +5196,6 @@
     private static final int MSG_INSETS_CHANGED = 30;
     private static final int MSG_INSETS_CONTROL_CHANGED = 31;
     private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
-    private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
     private static final int MSG_SHOW_INSETS = 34;
     private static final int MSG_HIDE_INSETS = 35;
     private static final int MSG_REQUEST_SCROLL_CAPTURE = 36;
@@ -5269,8 +5261,6 @@
                     return "MSG_INSETS_CONTROL_CHANGED";
                 case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED:
                     return "MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED";
-                case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED:
-                    return "MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED";
                 case MSG_SHOW_INSETS:
                     return "MSG_SHOW_INSETS";
                 case MSG_HIDE_INSETS:
@@ -5496,9 +5486,6 @@
                 case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
                     systemGestureExclusionChanged();
                 } break;
-                case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED: {
-                    updateLocationInParentDisplay(msg.arg1, msg.arg2);
-                } break;
                 case MSG_REQUEST_SCROLL_CAPTURE:
                     handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
                     break;
@@ -9030,17 +9017,6 @@
         mHandler.sendMessage(msg);
     }
 
-    /**
-     * Dispatch the offset changed.
-     *
-     * @param offset the offset of this view in the parent window.
-     */
-    public void dispatchLocationInParentDisplayChanged(Point offset) {
-        Message msg =
-                mHandler.obtainMessage(MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED, offset.x, offset.y);
-        mHandler.sendMessage(msg);
-    }
-
     public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
         synchronized (this) {
             mWindowFocusChanged = true;
@@ -9716,14 +9692,6 @@
         }
 
         @Override
-        public void locationInParentDisplayChanged(Point offset) {
-            final ViewRootImpl viewAncestor = mViewAncestor.get();
-            if (viewAncestor != null) {
-                viewAncestor.dispatchLocationInParentDisplayChanged(offset);
-            }
-        }
-
-        @Override
         public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index ffb7efa..d699194d 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -444,11 +444,6 @@
     }
 
     @Override
-    public void updateDisplayContentLocation(android.view.IWindow window, int x, int y,
-            int displayId) {
-    }
-
-    @Override
     public void updateTapExcludeRegion(android.view.IWindow window,
             android.graphics.Region region) {
     }
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index d661bc6..2c61280 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -110,7 +110,7 @@
             return null;
         }
         if (text instanceof Editable) {
-            return (Editable) super.getText();
+            return (Editable) text;
         }
         super.setText(text, BufferType.EDITABLE);
         return (Editable) super.getText();
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 69bc1b5..fd86769 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.graphics.Rect;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.WindowContainerToken;
 
 /**
@@ -39,12 +40,9 @@
 
     /**
      * Called when the Task want to remove the starting window.
-     * @param leash A persistent leash for the top window in this task.
-     * @param frame Window frame of the top window.
-     * @param playRevealAnimation Play vanish animation.
+     * @param removalInfo The information used to remove the starting window.
      */
-    void removeStartingWindow(int taskId, in SurfaceControl leash, in Rect frame,
-            in boolean playRevealAnimation);
+    void removeStartingWindow(in StartingWindowRemovalInfo removalInfo);
 
     /**
      * Called when the Task want to copy the splash screen.
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 10d21a0..5950e9f 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.pm.ActivityInfo;
@@ -34,7 +33,6 @@
  * start in the system.
  * @hide
  */
-@TestApi
 public final class StartingWindowInfo implements Parcelable {
     /**
      * Prefer nothing or not care the type of starting window.
diff --git a/core/java/android/window/StartingWindowRemovalInfo.aidl b/core/java/android/window/StartingWindowRemovalInfo.aidl
new file mode 100644
index 0000000..8e4ac04
--- /dev/null
+++ b/core/java/android/window/StartingWindowRemovalInfo.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/** @hide */
+parcelable StartingWindowRemovalInfo;
\ No newline at end of file
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
new file mode 100644
index 0000000..573db0d
--- /dev/null
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Information when removing a starting window of a particular task.
+ * @hide
+ */
+public final class StartingWindowRemovalInfo implements Parcelable {
+
+    /**
+     * The identifier of a task.
+     * @hide
+     */
+    public int taskId;
+
+    /**
+     * The animation container layer of the top activity.
+     * @hide
+     */
+    @Nullable
+    public SurfaceControl windowAnimationLeash;
+
+    /**
+     * The main window frame for the window of the top activity.
+     * @hide
+     */
+    @Nullable
+    public Rect mainFrame;
+
+    /**
+     * Whether need to play reveal animation.
+     * @hide
+     */
+    public boolean playRevealAnimation;
+
+    /**
+     * Whether need to defer removing the starting window for IME.
+     * @hide
+     */
+    public boolean deferRemoveForIme;
+
+    public StartingWindowRemovalInfo() {
+
+    }
+
+    private StartingWindowRemovalInfo(@NonNull Parcel source) {
+        readFromParcel(source);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    void readFromParcel(@NonNull Parcel source) {
+        taskId = source.readInt();
+        windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
+        mainFrame = source.readTypedObject(Rect.CREATOR);
+        playRevealAnimation = source.readBoolean();
+        deferRemoveForIme = source.readBoolean();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeTypedObject(windowAnimationLeash, flags);
+        dest.writeTypedObject(mainFrame, flags);
+        dest.writeBoolean(playRevealAnimation);
+        dest.writeBoolean(deferRemoveForIme);
+    }
+
+    @Override
+    public String toString() {
+        return "StartingWindowRemovalInfo{taskId=" + taskId
+                + " frame=" + mainFrame
+                + " playRevealAnimation=" + playRevealAnimation
+                + " deferRemoveForIme=" + deferRemoveForIme + "}";
+    }
+
+    public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR =
+            new Creator<StartingWindowRemovalInfo>() {
+                public StartingWindowRemovalInfo createFromParcel(@NonNull Parcel source) {
+                    return new StartingWindowRemovalInfo(source);
+                }
+                public StartingWindowRemovalInfo[] newArray(int size) {
+                    return new StartingWindowRemovalInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 845c13d..27c7d315 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -24,7 +24,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
-import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.SurfaceControl;
@@ -94,6 +93,7 @@
      * @param info The information about the Task that's available
      * @param appToken Token of the application being started.
      *        context to for resources
+     * @hide
      */
     @BinderThread
     public void addStartingWindow(@NonNull StartingWindowInfo info,
@@ -101,14 +101,11 @@
 
     /**
      * Called when the Task want to remove the starting window.
-     * @param leash A persistent leash for the top window in this task. Release it once exit
-     *              animation has finished.
-     * @param frame Window frame of the top window.
-     * @param playRevealAnimation Play vanish animation.
+     * @param removalInfo The information used to remove the starting window.
+     * @hide
      */
     @BinderThread
-    public void removeStartingWindow(int taskId, @Nullable SurfaceControl leash,
-            @Nullable Rect frame, boolean playRevealAnimation) {}
+    public void removeStartingWindow(@NonNull StartingWindowRemovalInfo removalInfo) {}
 
     /**
      * Called when the Task want to copy the splash screen.
@@ -257,10 +254,8 @@
         }
 
         @Override
-        public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-                boolean playRevealAnimation) {
-            mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId, leash, frame,
-                    playRevealAnimation));
+        public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
+            mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(removalInfo));
         }
 
         @Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b8dbfb3..9af4f91 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -901,7 +901,8 @@
         if (!isChangingConfigurations() && mPickOptionRequest != null) {
             mPickOptionRequest.cancel();
         }
-        if (mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+        if (mMultiProfilePagerAdapter != null
+                && mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
             mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
         }
     }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a00b993..bf094db 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -236,6 +236,8 @@
                 return "HIDE_TOGGLE_SOFT_INPUT";
             case SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API:
                 return "SHOW_SOFT_INPUT_BY_INSETS_API";
+            case SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE:
+                return "HIDE_DISPLAY_IME_POLICY_HIDE";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index e3713a3..9e57762 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -19,6 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
 import java.lang.annotation.Retention;
@@ -53,7 +54,8 @@
         SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY,
         SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT,
-        SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API})
+        SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API,
+        SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
     int SHOW_SOFT_INPUT = 0;
@@ -195,4 +197,10 @@
      * {@link android.view.InsetsController#show(int)};
      */
     int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
+
+    /**
+     * Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
+     * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
+     */
+    int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
 }
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 0307268..93baa19 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
@@ -32,8 +34,9 @@
     private final UsageBasedPowerEstimator mPowerEstimator;
 
     public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
+        // TODO(b/200239964): update to support multidisplay.
         mPowerEstimator = new UsageBasedPowerEstimator(
-                powerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY));
+                powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
     }
 
     /**
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index add2304..4d19b35 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -17,10 +17,12 @@
 package com.android.internal.os;
 
 
+import android.annotation.StringDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -40,6 +44,8 @@
  */
 public class PowerProfile {
 
+    public static final String TAG = "PowerProfile";
+
     /*
      * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode.
      * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should
@@ -145,12 +151,18 @@
 
     /**
      * Power consumption when screen is in doze/ambient/always-on mode, including backlight power.
+     *
+     * @deprecated Use {@link #POWER_GROUP_DISPLAY_AMBIENT} instead.
      */
+    @Deprecated
     public static final String POWER_AMBIENT_DISPLAY = "ambient.on";
 
     /**
      * Power consumption when screen is on, not including the backlight power.
+     *
+     * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_ON} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static final String POWER_SCREEN_ON = "screen.on";
 
@@ -175,7 +187,10 @@
     /**
      * Power consumption at full backlight brightness. If the backlight is at
      * 50% brightness, then this should be multiplied by 0.5
+     *
+     * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_FULL} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static final String POWER_SCREEN_FULL = "screen.full";
 
@@ -221,6 +236,29 @@
     public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
 
     /**
+     * Power consumption when a screen is in doze/ambient/always-on mode, including backlight power.
+     */
+    public static final String POWER_GROUP_DISPLAY_AMBIENT = "ambient.on.display";
+
+    /**
+     * Power consumption when a screen is on, not including the backlight power.
+     */
+    public static final String POWER_GROUP_DISPLAY_SCREEN_ON = "screen.on.display";
+
+    /**
+     * Power consumption of a screen at full backlight brightness.
+     */
+    public static final String POWER_GROUP_DISPLAY_SCREEN_FULL = "screen.full.display";
+
+    @StringDef(prefix = { "POWER_GROUP_" }, value = {
+            POWER_GROUP_DISPLAY_AMBIENT,
+            POWER_GROUP_DISPLAY_SCREEN_ON,
+            POWER_GROUP_DISPLAY_SCREEN_FULL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PowerGroup {}
+
+    /**
      * A map from Power Use Item to its power consumption.
      */
     static final HashMap<String, Double> sPowerItemMap = new HashMap<>();
@@ -255,6 +293,7 @@
                 readPowerValuesFromXml(context, forTest);
             }
             initCpuClusters();
+            initDisplays();
         }
     }
 
@@ -424,6 +463,58 @@
         return 0;
     }
 
+    private int mNumDisplays;
+
+    private void initDisplays() {
+        // Figure out how many displays are listed in the power profile.
+        mNumDisplays = 0;
+        while (!Double.isNaN(
+                getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, mNumDisplays, Double.NaN))
+                || !Double.isNaN(
+                getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, mNumDisplays, Double.NaN))
+                || !Double.isNaN(
+                getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, mNumDisplays,
+                        Double.NaN))) {
+            mNumDisplays++;
+        }
+
+        // Handle legacy display power constants.
+        final Double deprecatedAmbientDisplay = sPowerItemMap.get(POWER_AMBIENT_DISPLAY);
+        boolean legacy = false;
+        if (deprecatedAmbientDisplay != null && mNumDisplays == 0) {
+            final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_AMBIENT, 0);
+            Slog.w(TAG, POWER_AMBIENT_DISPLAY + " is deprecated! Use " + key + " instead.");
+            sPowerItemMap.put(key, deprecatedAmbientDisplay);
+            legacy = true;
+        }
+
+        final Double deprecatedScreenOn = sPowerItemMap.get(POWER_SCREEN_ON);
+        if (deprecatedScreenOn != null && mNumDisplays == 0) {
+            final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_ON, 0);
+            Slog.w(TAG, POWER_SCREEN_ON + " is deprecated! Use " + key + " instead.");
+            sPowerItemMap.put(key, deprecatedScreenOn);
+            legacy = true;
+        }
+
+        final Double deprecatedScreenFull = sPowerItemMap.get(POWER_SCREEN_FULL);
+        if (deprecatedScreenFull != null && mNumDisplays == 0) {
+            final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_FULL, 0);
+            Slog.w(TAG, POWER_SCREEN_FULL + " is deprecated! Use " + key + " instead.");
+            sPowerItemMap.put(key, deprecatedScreenFull);
+            legacy = true;
+        }
+        if (legacy) {
+            mNumDisplays = 1;
+        }
+    }
+
+    /**
+     * Returns the number built in displays on the device as defined in the power_profile.xml.
+     */
+    public int getNumDisplays() {
+        return mNumDisplays;
+    }
+
     /**
      * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a
      * default value if the subsystem has no recorded value.
@@ -496,6 +587,32 @@
     }
 
     /**
+     * Returns the average current in mA consumed by an ordinaled subsystem, or the given
+     * default value if the subsystem has no recorded value.
+     *
+     * @param group        the subsystem {@link PowerGroup}.
+     * @param ordinal      which entity in the {@link PowerGroup}.
+     * @param defaultValue the value to return if the subsystem has no recorded value.
+     * @return the average current in milliAmps.
+     */
+    public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal,
+            double defaultValue) {
+        final String type = getOrdinalPowerType(group, ordinal);
+        return getAveragePowerOrDefault(type, defaultValue);
+    }
+
+    /**
+     * Returns the average current in mA consumed by an ordinaled subsystem.
+     *
+     * @param group        the subsystem {@link PowerGroup}.
+     * @param ordinal      which entity in the {@link PowerGroup}.
+     * @return the average current in milliAmps.
+     */
+    public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal) {
+        return getAveragePowerForOrdinal(group, ordinal, 0);
+    }
+
+    /**
      * Returns the battery capacity, if available, in milli Amp Hours. If not available,
      * it returns zero.
      *
@@ -682,4 +799,9 @@
             }
         }
     }
+
+    // Creates the key for an ordinaled power constant from the group and ordinal.
+    private static String getOrdinalPowerType(@PowerGroup String group, int ordinal) {
+        return group + ordinal;
+    }
 }
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 1b3bc23..72ad4e7 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
@@ -50,10 +53,11 @@
     }
 
     public ScreenPowerCalculator(PowerProfile powerProfile) {
+        // TODO(b/200239964): update to support multidisplay.
         mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
-                powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
+                powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0));
         mScreenFullPowerEstimator = new UsageBasedPowerEstimator(
-                powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL));
+                powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
     }
 
     @Override
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index 8e454db..a8003a1 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -16,7 +16,7 @@
 package com.android.internal.policy;
 
 interface IKeyguardStateCallback {
-    void onShowingStateChanged(boolean showing);
+    void onShowingStateChanged(boolean showing, int userId);
     void onSimSecureStateChanged(boolean simSecure);
     void onInputRestrictedStateChanged(boolean inputRestricted);
     void onTrustedChanged(boolean trusted);
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index f212fc7..9e741e2 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -17,7 +17,6 @@
 package com.android.internal.view;
 
 import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.Point;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -62,10 +61,6 @@
     }
 
     @Override
-    public void locationInParentDisplayChanged(Point offset) {
-    }
-
-    @Override
     public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
     }
 
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 5636137..21c21a9 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"የስልክ ጥሪዎች ያድርጉ እና ያስተዳድሩ"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"የሰውነት ዳሳሾች"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ስለአስፈላጊ ምልክቶችዎ ያሉ የዳሳሽ ውሂብ ይድረሱ"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"ማሳወቂያዎች"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ማሳወቂያዎች አሳይ"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"የመስኮት ይዘት ሰርስረው ያውጡ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"መስተጋበር የሚፈጥሩት የመስኮት ይዘት ይመርምሩ።"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"በመንካት ያስሱን ያብሩ"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"መተግበሪያው መቆለፊያውና ማንኛውም የተጎዳኘ የይለፍ ቃል ደህንነት እንዲያሰናክል ይፈቅድለታል። ለምሳሌ ስልኩ ገቢ የስልክ ጥሪ በሚቀበልበት ጊዜ መቆለፊያውን ያሰናክልና ከዚያም ጥሪው ሲጠናቀቅ መቆለፊያውን በድጋሚ ያነቃዋል።"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"የማያ ገጽ መቆለፊያ ውስብስብነትን ጠይቅ"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"መተግበሪያው የማያ ገጽ መቆለፊያው ውስብስብነት ደረጃ (ከፍተኛ፣ መካከለኛ፣ ዝቅተኛ ወይም ምንም) እንዲያውቅ ያስችለዋል፣ ይህም ሊሆኑ የሚችለው የማያ ገጽ መቆለፊያው ርዝመት እና ዓይነት ክልል ያመለክታል። መተግበሪያው እንዲሁም ለተጠቃሚዎች የማያ ገጽ መቆለፊያውን ወደተወሰነ ደረጃ እንዲያዘምኑት ሊጠቁማቸው ይችላል። የማያ ገጽ መቆለፊያው በስነጣ አልባ ጽሑፍ እንደማይከማች ልብ ይበሉ፣ በዚህም መተግበሪያው ትክክለኛውን የይለፍ ቃል አያውቅም።"</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"ማሳወቂያዎች አሳይ"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"መተግበሪያው ማሳወቂያዎችን እንዲያሳይ ያስችለዋል"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"ባዮሜትራዊ ሃርድዌርን መጠቀም"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"መተግበሪያው የባዮሜትራዊ ሃርድዌር ለማረጋገጥ ስራ እንዲጠቀም ያስችለዋል"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"የጣት አሻራ ሃርድዌርን አስተዳድር"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1aaa75e..1b67328 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -338,10 +338,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"إجراء مكالمات هاتفية وإدارتها"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"أجهزة استشعار الجسم"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"الوصول إلى بيانات المستشعر حول علاماتك الحيوية"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"الإشعارات"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"عرض الإشعارات"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"استرداد محتوى النافذة"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"فحص محتوى نافذة يتم التفاعل معها"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"تفعيل الاستكشاف باللمس"</string>
@@ -565,10 +563,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"للسماح للتطبيق بإيقاف تأمين المفاتيح وأي أمان لكلمة مرور مرتبطة. على سبيل المثال، يعطل الهاتف تأمين المفاتيح عند استقبال مكالمة هاتفية واردة، ثم يعيد تفعيل تأمين المفاتيح عند انتهاء المكالمة."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"طلب معرفة مستوى صعوبة قفل الشاشة"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"للسماح للتطبيق بمعرفة مستوى صعوبة قفل الشاشة (عالي أو متوسط أو منخفض الصعوبة أو بدون)، والذي يحدّد النطاق المحتمل لطول ونوع قفل الشاشة. ويمكن أن يقترح التطبيق للمستخدمين أيضًا تعديل قفل الشاشة إلى مستوى معيّن، ولهم مطلق الحرية في تجاهل هذا الاقتراح ورفضه. وتجدر الإشارة إلى أنه لا يتم حفظ قفل الشاشة في نص عادي، لذا لا يعرف التطبيق كلمة المرور تحديدًا."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"عرض الإشعارات"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"السماح للتطبيق بعرض إشعارات"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"استخدام الأجهزة البيومترية"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"للسماح للتطبيق باستخدام الأجهزة البيومترية للمصادقة"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"لإدارة أجهزة بصمة الإصبع"</string>
diff --git a/core/res/res/values-as-television/strings.xml b/core/res/res/values-as-television/strings.xml
new file mode 100644
index 0000000..7585d9f
--- /dev/null
+++ b/core/res/res/values-as-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"মাইক্ৰ’ফ’ন অৱৰোধ কৰি থোৱা আছে"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"কেমেৰা অৱৰোধ কৰি থোৱা আছে"</string>
+</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2b057505..1b963f6 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2102,7 +2102,7 @@
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Това известие бе класирано по-високо. Докоснете, за да изпратите отзиви."</string>
     <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Това известие бе класирано по-ниско. Докоснете, за да изпратите отзиви."</string>
     <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Подобрени известия"</string>
-    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Предложените действия и отговори вече се предоставят от функцията за подобрени известия. Адаптивните известия за Android вече не се поддържат."</string>
+    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Предложените действия и отговори вече се предоставят от функцията за подо­брени известия. Адаптивните известия за Android вече не се поддържат."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Изключване"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Научете повече"</string>
diff --git a/core/res/res/values-bn-television/strings.xml b/core/res/res/values-bn-television/strings.xml
new file mode 100644
index 0000000..a2620e8
--- /dev/null
+++ b/core/res/res/values-bn-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"মাইক্রোফোন ব্লক করা আছে"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"ক্যামেরা ব্লক করা আছে"</string>
+</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 04c8bd5..0f47e29 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ফোন কলগুলি এবং পরিচালনা"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"বডি সেন্সর"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"আপনার অত্যাবশ্যক লক্ষণগুলির সম্পর্কে সেন্সর ডেটা অ্যাক্সেস করে"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"বিজ্ঞপ্তি"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"বিজ্ঞপ্তি দেখুন"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"উইন্ডোর কন্টেন্ট পুনরুদ্ধার করে"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ব্যবহার করছেন এমন একটি উইন্ডোর কন্টেন্ট নিরীক্ষণ করে৷"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"স্পর্শের মাধ্যমে অন্বেষণ করা চালু করুন"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"কী-লক এবং যেকোনো সংশ্লিষ্ট পাসওয়ার্ড সুরক্ষা অক্ষম করতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷ উদাহরণস্বরূপ, একটি ইনকামিং ফোন কল গ্রহণ করার সময়ে ফোনটি কী-লক অক্ষম করে, তারপরে কল শেষ হয়ে গেলে কী-লকটিকে আবার সক্ষম করে৷"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"স্ক্রিন লকের জটিলতা জানার অনুরোধ করুন"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"এটি অ্যাপটিকে স্ক্রিন লকের জটিলতার লেভেল বুঝতে সাহায্য করে (খুব বেশি, মাঝারি, অল্প জটিল বা কোনও জটিলতা নেই), যা স্ক্রিন লকটি সম্ভবত কত দীর্ঘ ও সেটির ধরন কীরকম, তার ইঙ্গিত দেয়। এই অ্যাপটি একটি নির্দিষ্ট লেভেল পর্যন্ত স্ক্রিন লক আপডেট করার সাজেশনও দিতে পারে, তবে ব্যবহারকারী তা উপেক্ষা করে অন্য কোথাও চলে যেতে পারেন। মনে রাখবেন যে স্ক্রিন লক প্লেন টেক্সট হিসেবে সংরক্ষণ করা হয় না, তাই অ্যাপ কখনও আসল পাসওয়ার্ড জানতে পারে না।"</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"বিজ্ঞপ্তি দেখুন"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"অ্যাপকে বিজ্ঞপ্তি দেখানোর জন্য অনুমতি দেয়"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"বায়োমেট্রিক হার্ডওয়্যার ব্যবহার করুন"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"অ্যাপটিকে যাচাইকরণের জন্য বায়োমেট্রিক হার্ডওয়্যার ব্যবহার করার অনুমতি দেয়"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"আঙ্গুলের ছাপ নেওয়ার হার্ডওয়্যার পরিচালনা করুন"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 1bd7970..bcbf238 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"Telefonanrufe tätigen und verwalten"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Körpersensoren"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"auf Sensordaten zu deinen Vitaldaten zugreifen"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"Benachrichtigungen"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"Benachrichtigungen anzeigen"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Fensterinhalte abrufen"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Die Inhalte eines Fensters, mit dem du interagierst, werden abgerufen."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\"Tippen &amp; Entdecken\" aktivieren"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ermöglicht der App, die Tastensperre sowie den damit verbundenen Passwortschutz zu deaktivieren. Das Telefon deaktiviert die Tastensperre beispielsweise, wenn ein Anruf eingeht, und aktiviert sie wieder, nachdem das Gespräch beendet wurde."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"Komplexitätsstufe der Displaysperre anfragen"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Ermöglicht es der App, die Komplexitätsstufe der Displaysperre (hoch, mittel, niedrig oder keine) zu ermitteln, was auf Art und mögliche Dauer der Displaysperre hinweist. Die App kann Nutzern auch vorschlagen, die Displaysperre auf eine bestimmte Stufe zu setzen. Nutzer können diesen Vorschlag jedoch einfach ignorieren und fortfahren. Beachten Sie, dass die Displaysperre nicht im Klartext gespeichert ist, sodass die App nicht das genaue Passwort kennt."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"Benachrichtigungen anzeigen"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"Ermöglicht der App, Benachrichtigungen anzuzeigen"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"Biometrische Hardware verwenden"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Erlaubt der App, biometrische Hardware zur Authentifizierung zu verwenden"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"Fingerabdruckhardware verwalten"</string>
diff --git a/core/res/res/values-fr-rCA-television/strings.xml b/core/res/res/values-fr-rCA-television/strings.xml
new file mode 100644
index 0000000..3187a48
--- /dev/null
+++ b/core/res/res/values-fr-rCA-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Le microphone est bloqué"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"La caméra est bloquée"</string>
+</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index a8023cc..43d6c09 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"faire et gérer des appels téléphoniques"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Capteurs corporels"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"accéder aux données des capteurs sur vos signes vitaux"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notifications"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"afficher les notifications"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Récupérer le contenu d\'une fenêtre"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspecter le contenu d\'une fenêtre avec laquelle vous interagissez."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activer la fonctionnalité Explorer au toucher"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Permet à l\'application de désactiver le verrouillage des touches et toute mesure de sécurité par mot de passe associée. Par exemple, votre téléphone désactive le verrouillage des touches lorsque vous recevez un appel, puis le réactive lorsque vous raccrochez."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"demander la complexité du verrouillage d\'écran"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Autorise l\'application à apprendre le niveau de complexité de l\'écran de verrouillage (élevé, moyen, faible ou aucun), qui indique la gamme possible de longueur et de type de verrouillage d\'écran. L\'application peut aussi suggérer aux utilisateurs de mettre à jour l\'écran de verrouillage afin d\'utiliser un certain niveau de complexité, mais ils peuvent ignorer la suggestion. Notez que le verrouillage d\'écran n\'est pas stocké en texte brut pour de manière à ce que l\'application n\'ait pas accès au mot de passe exact."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"afficher les notifications"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"Permet à l\'application d\'afficher les notifications"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"utiliser le matériel biométrique"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Permet à l\'application d\'utiliser du matériel biométrique pour l\'authentification"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"gérer le matériel d\'empreinte digitale"</string>
diff --git a/core/res/res/values-fr-television/strings.xml b/core/res/res/values-fr-television/strings.xml
new file mode 100644
index 0000000..2e357e6
--- /dev/null
+++ b/core/res/res/values-fr-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"Le micro est bloqué"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"La caméra est bloquée"</string>
+</resources>
diff --git a/core/res/res/values-gu-television/strings.xml b/core/res/res/values-gu-television/strings.xml
new file mode 100644
index 0000000..ff36196
--- /dev/null
+++ b/core/res/res/values-gu-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"કૅમેરાને બ્લૉક કરવામાં આવ્યો છે"</string>
+</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 02b1787..5b0682b 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ફોન કૉલ કરો અને મેનેજ કરો"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"બૉડી સેન્સર"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"તમારા મહત્વપૂર્ણ ચિહ્નો વિશે સેન્સર ડેટા ઍક્સેસ કરો"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"નોટિફિકેશન"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"નોટિફિકેશન બતાવો"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"વિંડો કન્ટેન્ટ પુનઃપ્રાપ્ત કરો"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"તમે જેની સાથે ક્રિયા-પ્રતિક્રિયા કરી રહ્યાં છો તે વિંડોનું કન્ટેન્ટ તપાસો."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"સ્પર્શ કરીને શોધખોળ કરવું ચાલુ કરો"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"એપ્લિકેશનને કીલૉક અને કોઈપણ સંકળાયેલ પાસવર્ડ સુરક્ષા અક્ષમ કરવાની મંજૂરી આપે છે. ઉદાહરણ તરીકે, ઇનકમિંગ ફોન કૉલ પ્રાપ્ત કરતી વખતે ફોન, કીલૉકને અક્ષમ કરે છે, પછી કૉલ સમાપ્ત થઈ જવા પર કીલૉક ફરીથી સક્ષમ કરે છે."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"સ્ક્રીન લૉકની જટિલતા જાણવા માટે વિનંતી કરો"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"ઍપને સ્ક્રીન લૉકની જટિલતાનું લેવલ (ઊંચું, મધ્યમ, નીચું અથવા કોઈ નહીં) જાણવાની મંજૂરી આપે છે, જે સ્ક્રીન લૉકના પ્રકાર અને લંબાઈની સંભવિત શ્રેણી સૂચવે છે. ઍપ વપરાશકર્તાઓને સ્ક્રીન લૉકને ચોક્કસ લેવલ સુધી અપડેટ કરવાનું સૂચન પણ કરી શકે છે, પરંતુ વપરાશકર્તાઓ મુક્ત રીતે અવગણીને નૅવિગેટ કરી શકે છે. નોંધી લો કે સ્ક્રીન લૉકનો plaintextમાં સંગ્રહ કરવામાં આવતો નથી, તેથી ઍપને ચોક્કસ પાસવર્ડની જાણ હોતી નથી."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"નોટિફિકેશન બતાવો"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ઍપને નોટિફિકેશન બતાવવાની મંજૂરી આપે છે"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"બાયોમેટ્રિક હાર્ડવેરનો ઉપયોગ કરો"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ઍપને પ્રમાણીકરણ માટે બાયોમેટ્રિક હાર્ડવેરનો ઉપયોગ કરવાની મંજૂરી આપે છે"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ફિંગરપ્રિન્ટ હાર્ડવેરને મેનેજ કરો"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 3b55118..52539e0 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -332,10 +332,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ביצוע וניהול של שיחות טלפון"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"חיישנים גופניים"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"גישה אל נתוני חיישנים של הסימנים החיוניים שלך"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"התראות"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"הצגת התראות"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"אחזור תוכן של חלון"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"בדיקת התוכן של חלון שאיתו מתבצעת אינטראקציה."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"הפעלה של \'גילוי באמצעות מגע\'"</string>
@@ -559,10 +557,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"מאפשרת לאפליקציה להשבית את נעילת המקשים וכל אמצעי אבטחה משויך המבוסס על סיסמה. לדוגמה, הטלפון ישבית את נעילת המקשים במהלך שיחת טלפון נכנסת, ויפעיל מחדש את נעילת המקשים עם סיום השיחה."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"בקשת מידע לגבי מידת המורכבות של נעילת המסך"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"מאפשרת לאפליקציה ללמוד את רמת המורכבות של נעילת המסך (גבוהה, בינונית, נמוכה או לא מורכבת). הרמה הזו מציינת את הטווח האפשרי של אורך וסוג נעילת המסך. האפליקציה יכולה גם להציע למשתמשים לעדכן את נעילת המסך לרמה מסוימת, אבל המשתמשים יכולים להתעלם מההצעה ולנווט לפריט אחר. לתשומת ליבך, נעילת המסך לא מאוחסנת כטקסט פשוט, ולכן האפליקציה לא יודעת מה הסיסמה המדויקת."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"הצגת התראות"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"מאפשרת לאפליקציה להציג התראות"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"שימוש בחומרה ביומטרית"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"מאפשרת לאפליקציה להשתמש בחומרה ביומטרית לצורך אימות"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ניהול חומרה של טביעות אצבעות"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 2a9a59b..c8edc77 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ಫೋನ್ ಕರೆ ಮಾಡಲು ಹಾಗೂ ನಿರ್ವಹಿಸಲು"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"ಬಾಡಿ ಸೆನ್ಸರ್‌"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ನಿಮ್ಮ ಮುಖ್ಯ ಲಕ್ಷಣಗಳ ಕುರಿತು ಸೆನ್ಸಾರ್ ಡೇಟಾವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"ಅಧಿಸೂಚನೆಗಳು"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಿ"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ವಿಂಡೋ ವಿಷಯವನ್ನು ಹಿಂಪಡೆಯುತ್ತದೆ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ನೀವು ಬಳಸುತ್ತಿರುವ ವಿಂಡೋದ ವಿಷಯ ಪರೀಕ್ಷಿಸುತ್ತದೆ."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"ಸ್ಪರ್ಶ-ಎಕ್ಸ್‌ಪ್ಲೋರ್ ಆನ್ ಮಾಡುತ್ತದೆ"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ಕೀಲಾಕ್ ಮತ್ತು ಯಾವುದೇ ಸಂಬಂಧಿತ ಭದ್ರತಾ ಪಾಸ್‍‍ವರ್ಡ್ ಭದ್ರತೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿ ನೀಡುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಒಳಬರುವ ಕರೆಯನ್ನು ಸ್ವೀಕರಿಸುವಾಗ ಕೀಲಾಕ್ ಅನ್ನು ಫೋನ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ, ನಂತರ ಕರೆಯು ಅಂತ್ಯಗೊಂಡಾಗ ಕೀಲಾಕ್ ಅನ್ನು ಮರು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸಂಕೀರ್ಣತೆಯನ್ನು ವಿನಂತಿಸಿ"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"ಆ್ಯಪ್‌ಗೆ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಸಂಕೀರ್ಣತೆ ಮಟ್ಟವನ್ನು (ಅಧಿಕ, ಮಧ್ಯಮ, ಕಡಿಮೆ ಅಥವಾ ಯಾವುದೂ ಅಲ್ಲ) ತಿಳಿದುಕೊಳ್ಳಲು ಅನುಮತಿಸುತ್ತದೆ, ಅದು ಉದ್ದದ ಸಂಭವನೀಯ ಶ್ರೇಣಿ ಮತ್ತು ಸ್ಕ್ರೀನ್ ಲಾಕ್‌ನ ವಿಧವನ್ನು ಸೂಚಿಸುತ್ತದೆ. ಬಳಕೆದಾರರು ನಿರ್ದಿಷ್ಟ ಹಂತದವರೆಗೆ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಬಹುದು, ಆದರೆ ಅವರು ಅದನ್ನು ನಿರ್ಲಕ್ಷಿಸಬಹುದು ಮತ್ತು ಅದರಿಂದ ಹೊರಬರಬಹುದು ಎಂಬುದನ್ನು ಸಹ ಆ್ಯಪ್ ಬಳಕೆದಾರರಿಗೆ ಸೂಚಿಸುತ್ತದೆ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಪಠ್ಯದ ರೂಪದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವುದಿಲ್ಲ, ಇದರಿಂದಾಗಿ ಆ್ಯಪ್‌ಗೆ ಸರಿಯಾದ ಪಾಸ್‌ವರ್ಡ್ ಗೊತ್ತಿರುವುದಿಲ್ಲ ಎಂಬುದನ್ನು ಗಮನಿಸಿ."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಿ"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"ಬಯೋಮೆಟ್ರಿಕ್ ಹಾರ್ಡ್‌ವೇರ್‌ ಬಳಸಿ"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ಪ್ರಮಾಣೀಕರಣಕ್ಕಾಗಿ ಬಯೋಮೆಟ್ರಿಕ್ ಹಾರ್ಡ್‌ವೇರ್ ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಹಾರ್ಡ್‌ವೇರ್ ನಿರ್ವಹಿಸಿ"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index cd611f7..fe39b00 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ໂທ ແລະ​ຈັດ​ການ​ການ​ໂທ​ລະ​ສັບ"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"ເຊັນເຊີຮ່າງກາຍ"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ເຂົ້າ​ຫາ​ຂໍ້​ມູນ​ເຊັນ​ເຊີ​ກ່ຽວ​ກັບ​ສັນ​ຍານ​ຊີບ​ຂອງ​ທ່ານ"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"ການແຈ້ງເຕືອນ"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ສະແດງການແຈ້ງເຕືອນ"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ດຶງຂໍ້ມູນເນື້ອຫາໃນໜ້າຈໍ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ກວດກາເນື້ອຫາຂອງໜ້າຈໍທີ່ທ່ານກຳລັງມີປະຕິສຳພັນນຳ."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"ເປີດໃຊ້ \"ການສຳຫຼວດໂດຍສຳຜັດ\""</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ອະນຸຍາດໃຫ້ແອັບຯປິດການເຮັດວຽກຂອງປຸ່ມລັອກ ແລະລະບົບຄວາມປອດໄພຂອງລະຫັດຜ່ານທີ່ເຊື່ອມໂຍງກັນ. ໂຕຢ່າງ: ໂທລະສັບຈະປິດການເຮັດວຽກຂອງປຸ່ມລັອກເມື່ອມີສາຍໂທເຂົ້າ ຈາກນັ້ນຈຶ່ງເປີດໃຊ້ໄດ້ອີກເມື່ອວາງສາຍແລ້ວ."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ຮ້ອງຂໍຄວາມຊັບຊ້ອນການລັອກໜ້າຈໍ"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"ອະນຸຍາດໃຫ້ແອັບສຶກສາລະດັບຄວາມຊັບຊ້ອນຂອງໜ້າຈໍລັອກ (ສູງ, ກາງ ຫຼື ບໍ່ມີ), ເຊິ່ງລະບຸຂອບເຂດຄວາມເປັນໄປໄດ້ຂອງຄວາມຍາວ ແລະ ປະເພດຂອງການລັອກໜ້າຈໍ. ແອັບນີ້ສາມາດແນະນຳຜູ້ໃຊ້ວ່າເຂົາເຈົ້າສາມາດອັບເດດໜ້າຈໍລັອກເປັນລະດັບໃດໜຶ່ງເປັນການສະເພາະໄດ້, ແຕ່ຜູ້ໃຊ້ສາມາດທີ່ຈະບໍ່ສົນໃຈ ຫຼື ເປີດໄປອັນອື່ນໄດ້. ກະລຸນາຮັບຊາບວ່າການລັອກໜ້າຈໍບໍ່ໄດ້ບັນທຶກໃນແບບຂໍ້ຄວາມທຳມະດາ, ດັ່ງນັ້ນແອັບຈະບໍ່ຮູ້ລະຫັດຜ່ານທີ່ແນ່ນອນ."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"ສະແດງການແຈ້ງເຕືອນ"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ອະນຸຍາດໃຫ້ແອັບສະແດງການແຈ້ງເຕືອນ"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"ໃຊ້ຮາດແວຊີວະມິຕິ"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ອະນຸຍາດໃຫ້ແອັບນຳໃຊ້ຮາດແວຊີວະມິຕິສຳລັບການພິສູດຢືນຢັນ"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ຈັດ​ການ​ຮາດ​ແວ​ລາຍ​ນີ້ວ​ມື"</string>
diff --git a/core/res/res/values-mcc334-mnc020-as/strings.xml b/core/res/res/values-mcc334-mnc020-as/strings.xml
index 25b074e..43f5fb1 100644
--- a/core/res/res/values-mcc334-mnc020-as/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-as/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ বিফল হৈছে -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"সেৱাটো ছাবস্ক্ৰাইব কৰা নাই -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"এটা APNৰ বাবে একাধিক PDN সংযোগৰ অনুমতি নাই -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-bn/strings.xml b/core/res/res/values-mcc334-mnc020-bn/strings.xml
index 25b074e..f58aea7 100644
--- a/core/res/res/values-mcc334-mnc020-bn/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-bn/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"যাচাই করা যায়নি -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"পরিষেবা সাবস্ক্রাইব করা হয়নি -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"নির্দিষ্ট কোনও APN-এর জন্য একাধিক PDN কানেকশন অনুমোদিত নয় -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-rCA/strings.xml b/core/res/res/values-mcc334-mnc020-fr-rCA/strings.xml
index 25b074e..19794da 100644
--- a/core/res/res/values-mcc334-mnc020-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-fr-rCA/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"ÉCHEC DE L\'AUTHENTIFICATION -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"NON ABONNÉ AU SERVICE -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Connexions PDN multiples interdites pour un APN donné -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr/strings.xml b/core/res/res/values-mcc334-mnc020-fr/strings.xml
index 25b074e..6eaa7cd 100644
--- a/core/res/res/values-mcc334-mnc020-fr/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-fr/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"ÉCHEC DE L\'AUTHENTIFICATION -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"NON INSCRIT AU SERVICE -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisé à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-gu/strings.xml b/core/res/res/values-mcc334-mnc020-gu/strings.xml
index 25b074e..5faef6f 100644
--- a/core/res/res/values-mcc334-mnc020-gu/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-gu/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"પ્રમાણીકરણ નિષ્ફળ થયું -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"સેવા સબ્સ્ક્રાઇબ કરી નથી -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"આપેલા APN માટે એક કરતાં વધારે PDN કનેક્શનની મંજૂરી નથી -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-ur/strings.xml b/core/res/res/values-mcc334-mnc020-ur/strings.xml
index 25b074e..bab5589 100644
--- a/core/res/res/values-mcc334-mnc020-ur/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-ur/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"توثیق کی ناکامی -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"سروس کو سبسکرائب نہیں کیا -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"‏ایک دیئے گئے APN کے لیے متعدد PDN کنکشنز کی اجازت نہیں ہے -55-"</string>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc020-zh-rCN/strings.xml b/core/res/res/values-mcc334-mnc020-zh-rCN/strings.xml
index 25b074e..a8fbe74c 100644
--- a/core/res/res/values-mcc334-mnc020-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc334-mnc020-zh-rCN/strings.xml
@@ -20,10 +20,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="config_pdp_reject_dialog_title" msgid="41208110171880430"></string>
-    <!-- no translation found for config_pdp_reject_user_authentication_failed (4683454131283459978) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_service_not_subscribed (9021140729932308119) -->
-    <skip />
-    <!-- no translation found for config_pdp_reject_multi_conn_to_same_pdn_not_allowed (3838388706348367865) -->
-    <skip />
+    <string name="config_pdp_reject_user_authentication_failed" msgid="4683454131283459978">"身份验证失败 -29-"</string>
+    <string name="config_pdp_reject_service_not_subscribed" msgid="9021140729932308119">"未订阅服务 -33-"</string>
+    <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"指定的 APN 不能有多个 PDN 连接 -55-"</string>
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index b63447a..1dfb50b 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ഫോൺ വിളിക്കുകയും നിയന്ത്രിക്കുകയും ചെയ്യുക"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"ബോഡി സെൻസർ"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"നിങ്ങളുടെ ജീവാധാര ലക്ഷണങ്ങളെ കുറിച്ചുള്ള സെൻസർ വിവരങ്ങൾ ആക്സസ് ചെയ്യുക"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"അറിയിപ്പുകൾ"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"അറിയിപ്പുകൾ കാണിക്കുക"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"വിൻഡോ ഉള്ളടക്കം വീണ്ടെടുക്കുക"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"നിങ്ങൾ സംവദിക്കുന്ന ഒരു വിൻഡോയുടെ ഉള്ളടക്കം പരിശോധിക്കുക."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"സ്‌പർശനം വഴി പര്യവേക്ഷണം ചെയ്യുക, ഓണാക്കുക"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"കീലോക്കും ഏതെങ്കിലും അനുബന്ധ പാസ്‌വേഡ് സുരക്ഷയും പ്രവർത്തനരഹിതമാക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഉദാഹരണത്തിന്, ഒരു ഇൻകമിംഗ് കോൾ സ്വീകരിക്കുമ്പോൾ ഫോൺ കീലോക്ക് പ്രവർത്തനരഹിതമാക്കുന്നു, കോൾ അവസാനിക്കുമ്പോൾ കീലോക്ക് വീണ്ടും പ്രവർത്തനക്ഷമമാകുന്നു."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"സ്‌ക്രീൻ ലോക്ക് സങ്കീർണ്ണത അഭ്യർത്ഥിക്കുക"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"സ്ക്രീൻ ലോക്കിന്റെ സാധ്യമായ നീളവും തരവും സൂചിപ്പിക്കുന്ന, അതിന്റെ സങ്കീർണ്ണത നില (ഉയർന്നത്, ഇടത്തരം, കുറഞ്ഞത് അല്ലെങ്കിൽ ഒന്നുമില്ല) മനസ്സിലാക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. സ്‌ക്രീൻ ലോക്ക് ഒരു പ്രത്യേക തലത്തിലേക്ക് അപ്ഡേറ്റ് ചെയ്യാൻ ഉപയോക്താക്കളെ നിർദ്ദേശിക്കാനും ആപ്പിനാവും, പക്ഷെ ഉപയോക്താക്കൾക്ക് എളുപ്പത്തിൽ അവഗണിക്കാനും മറ്റൊന്നിലേക്ക് നാവിഗേറ്റ് ചെയ്യാനുമാവും. പ്ലെയിൻടെക്‌സ്‌റ്റിൽ സ്ക്രീൻ ലോക്ക് സംഭരിക്കപ്പെട്ടിട്ടില്ലെന്ന കാര്യം ശ്രദ്ധിക്കുക, അതിനാൽ ആപ്പിന് കൃത്യമായ പാസ്‌വേഡ് അറിയില്ല."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"അറിയിപ്പുകൾ കാണിക്കുക"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"അറിയിപ്പുകൾ കാണിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"ബയോമെട്രിക് ഹാർ‌ഡ്‌വെയർ ഉപയോഗിക്കുക"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"പരിശോധിച്ചുറപ്പിക്കുന്നതിനായി, ബയോമെട്രിക് ഹാർഡ്‌വെയർ ഉപയോഗിക്കാൻ ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ഫിംഗർപ്രിന്റ് ഹാർഡ്‌വെയർ നിയന്ത്രിക്കുക"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 4da28dc..5740a61 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"फोन कॉल आणि व्यवस्थापित"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"शरीर सेन्सर"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"आपल्‍या महत्त्वाच्या मापनांविषयी सेन्सर डेटा अ‍ॅक्सेस करा"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"सूचना"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"सूचना दाखवा"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"विंडोमधील आशय पुन्हा मिळवा"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"तुम्ही वापरत असलेल्‍या विंडोमधील आशय तपासा."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"स्पर्श करून अन्वेषण सुरू करा"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"कीलॉक आणि कोणतीही संबद्ध पासवर्ड सुरक्षितता अक्षम करण्यासाठी अ‍ॅप ला अनुमती देते. उदाहरणार्थ, येणारा फोन कॉल प्राप्त करताना फोन कीलॉक अक्षम करतो, नंतर जेव्हा कॉल समाप्त होतो तेव्हा तो कीलॉक पुन्हा-सक्षम करतो."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"स्क्रीन लॉक क्लिष्टतेची विनंती करा"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"अ‍ॅपला स्क्रीन लॉक क्लिष्टता पातळी (उच्च, मध्यम, खालची किंवा काहीही नाही) जाणून घेऊ देते, जी लांबीची संभाव्य रेंज आणि स्क्रीन लॉकचा प्रकार सूचित करते. अ‍ॅप वापरकर्त्यांना असेदेखील सुचवू शकते की त्यांनी स्क्रीन लॉक ठराविक पातळीपर्यंत अपडेट करावे, परंतु वापरकर्ते त्याकडे मोकळेपणाने दुर्लक्ष करू शकतात आणि तेथून नेव्हिगेट करू शकतात. स्क्रीन लॉक प्लेनटेक्स्टमध्ये स्टोअर केले जात नसल्यामुळे अ‍ॅपला नेमका पासवर्ड माहीत नसतो याची नोंद घ्या."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"सूचना दाखवा"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ॲपला सूचना दाखवू देते"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"बायोमेट्रिक हार्डवेअर वापरा"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ऑथेंटिकेशनसाठी बायोमेट्रिक हार्डवेअरचा वापर करण्याची ॲपला अनुमती देते"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"फिंगरप्रिंट हार्डवेअर व्यवस्थापित करा"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index e5e78ee..edc206b 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ଫୋନ୍‍ କଲ୍‍ କରେ ଏବଂ ପରିଚାଳନା କରେ"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"ବଡି ସେନ୍ସର୍"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ଆପଣଙ୍କ ଗୁରୁତପୂର୍ଣ୍ଣ ସଂକେତଗୁଡ଼ିକ ବିଷୟରେ ସେନ୍ସର୍‍ ଡାଟା ଆକ୍ସେସ୍‍ କରେ"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାନ୍ତୁ"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ୱିଣ୍ଡୋ କଣ୍ଟେଣ୍ଟ ହାସଲ କରନ୍ତୁ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ଆପଣ କାମ କରୁଥିବା ୱିଣ୍ଡୋର କଣ୍ଟେଣ୍ଟକୁ ଯାଞ୍ଚ କରନ୍ତୁ।"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"ସ୍ପର୍ଶ ଦ୍ୱାରା ଏକ୍ସପ୍ଲୋର୍‍ ଅନ୍‍ କରନ୍ତୁ"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ଆପ୍‌କୁ କୀ\'ଲକ୍ କିମ୍ବା ସେଥିରେ ଥିବା କୌଣସି ପାସ୍‌ୱର୍ଡ ସୁରକ୍ଷାକୁ ଅକ୍ଷମ କରିବା ପାଇଁ ଅନୁମତି ଦିଏ, ଉଦାହରଣସ୍ୱରୂପ, ଇନ୍‌କମିଙ୍ଗ ଫୋନ୍‌ କଲ୍ ପ୍ରାପ୍ତ କରିବା ସମୟରେ ଫୋନ୍‌ଟି କୀ\'ଲକ୍‌କୁ ଅକ୍ଷମ କରିଦିଏ, ତା’ପରେ କଲ୍ ସମାପ୍ତ ହେବାପରେ ପୁଣି କୀ\'ଲକ୍‌କୁ ସକ୍ଷମ କରିଥାଏ।"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"ସ୍କ୍ରିନ୍ ଲକ୍ ଜଟିଳତା ସଂକ୍ରାନ୍ତ ଅନୁରୋଧ"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"ସ୍କ୍ରିନ୍ ଲକ୍‌ର ଜଟିଳତା ସ୍ତର (ଉଚ୍ଚ, ମଧ୍ୟମ, ନିମ୍ନ କିମ୍ବା କିଛିନୁହେଁ), ଜାଣିବାକୁ ଆପ୍‌କୁ ଅନୁମତି ଦିଅନ୍ତୁ, ଯାହା ସ୍କ୍ରିନ୍ ଲକ୍‌ର ସମ୍ଭାବ୍ୟ ପରିସୀମାର ଲମ୍ବ ଏବଂ ପ୍ରକାର ସୂଚୀତ କରେ। ଆପ୍ ଏହା ମଧ୍ୟ ଉପଯୋଗକର୍ତ୍ତାମାନଙ୍କୁ ପରାମର୍ଶ ଦେଇପାରେ ଯେ ସେମାନେ ସ୍କ୍ରିନ୍ ଲକ୍‌କୁ ଏକ ନିର୍ଦ୍ଧିଷ୍ଟ ସ୍ତର ପର୍ଯ୍ୟନ୍ତ ଅପ୍‌ଡେଟ୍ କରିପାରନ୍ତି, କିନ୍ତୁ ଉପଯୋଗକର୍ତ୍ତାମାନେ ନିଜ ଇଚ୍ଛାରେ ଏହାକୁ ଉପେକ୍ଷା ଏବଂ ନାଭିଗେଟ୍ କରିପାରିବେ। ଧ୍ୟାନ ଦିଅନ୍ତୁ ଯେ, ସ୍କ୍ରିନ୍ ଲକ୍ ସରଳ ଟେକ୍ସଟ୍‌ରେ ଷ୍ଟୋର୍ କରାଯାଇନଥାଏ ତେଣୁ ଆପ୍ ସଠିକ୍ ପାସ୍‌‍ୱର୍ଡ ଜାଣିପାରି ନଥାଏ।"</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାନ୍ତୁ"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଇବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"ବାୟୋମେଟ୍ରିକ୍‌ ହାର୍ଡୱେର୍‌ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ସ୍ୱୀକୃତି ପାଇଁ ବାୟୋମେଟ୍ରିକ୍‌ ହାର୍ଡୱେର୍‌ ବ୍ୟବହାର କରିବାକୁ ଅନୁମତି ଦିଏ"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"ଟିପଚିହ୍ନ ହାର୍ଡୱେର୍‍ ପରିଚାଳନା କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 4426146..17e1588 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -332,10 +332,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"opravljanje in upravljanje telefonskih klicev"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Tipala telesnih funkcij"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"dostop do podatkov tipala o vaših vitalnih znakih"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"Obvestila"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikaz obvestil"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Pridobiti vsebino okna"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Preverjanje vsebine okna, ki ga uporabljate."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Vklopiti raziskovanje z dotikom"</string>
@@ -559,10 +557,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Aplikaciji dovoljuje, da onemogoči zaklep tipkovnice in morebitno povezano varnostno geslo. Telefon na primer onemogoči zaklep tipkovnice pri dohodnem klicu ter vnovič omogoči zaklep, ko je klic končan."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"zahteva zapletenost zaklepanja zaslona"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Aplikaciji dovoljuje, da pridobi raven zapletenosti zaklepanja zaslona (visoka, srednja, nizka ali brez), ki označuje možen obseg dolžine in vrsto zaklepanja zaslona. Aplikacija lahko tudi predlaga uporabnikom, da posodobijo zaklepanje zaslona na določeno raven, vendar lahko uporabniki to opozorilo prezrejo in ga zaprejo tako, da se pomaknejo stran. Upoštevajte, da zaklepanje zaslona ni shranjeno v golem besedilu, tako da aplikacija ne pozna točnega gesla."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"prikaz obvestil"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"Aplikaciji dovoli prikaz obvestil."</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"uporaba strojne opreme za biometrične podatke"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Aplikaciji omogoča uporabo strojne opreme za biometrične podatke za preverjanje pristnosti"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"upravljanje strojne opreme za prstne odtise"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 0f6d47e..c216de3 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"ఫోన్ కాల్స్‌ చేయడం మరియు నిర్వహించడం"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"శరీర సెన్సార్‌లు"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"మీ అత్యంత కీలకమైన గుర్తుల గురించి సెన్సార్ డేటాని యాక్సెస్ చేస్తుంది"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"నోటిఫికేషన్‌లు"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"నోటిఫికేషన్‌లను చూపండి"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"విండో కంటెంట్‍ను తిరిగి పొందుతుంది"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"మీరు పరస్పర చర్య చేస్తున్న విండో కంటెంట్‌‍ను పరిశీలిస్తుంది."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"తాకడం ద్వారా విశ్లేషణను ప్రారంభిస్తుంది"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"కీలాక్ మరియు ఏదైనా అనుబంధించబడిన పాస్‌వర్డ్ భద్రతను నిలిపివేయడానికి యాప్‌ను అనుమతిస్తుంది. ఉదాహరణకు, ఇన్‌కమింగ్ ఫోన్ కాల్ వస్తున్నప్పుడు ఫోన్ కీలాక్‌ను నిలిపివేస్తుంది, ఆపై కాల్ ముగిసిన తర్వాత కీలాక్‌ను మళ్లీ ప్రారంభిస్తుంది."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"స్క్రీన్ లాక్ సంక్లిష్టత రిక్వెస్ట్‌"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"ఇది మీ స్క్రీన్ లాక్ పాస్‌వర్డ్‌ సంక్లిష్టత స్థాయి (తీవ్రంగా ఉండాలా, ఓ మోస్తరుగా ఉండాలా, తక్కువ తీవ్రంగా ఉండాలా లేదా అస్సలు తీవ్రత ఉండకూడదా) తెలుసుకోవడానికి యాప్‌ను అనుమతిస్తుంది, అంటే పొడుగు ఎంత ఉండాలి, ఏ రకమైన స్క్రీన్ లాక్ పధ్ధతి అనుసరించాలో సూచిస్తుంది. అలాగే, స్క్రీన్ లాక్‌ పాస్‌వర్డ్‌ సంక్లిష్టతను ఏ స్థాయికి సెట్ చేసుకుంటే బాగుంటుందో కూడా వినియోగదారులకు యాప్ సూచించగలదు, కానీ వినియోగదారులు నిరభ్యంతరంగా ఆ సూచనలను పట్టించుకోకుండా వారి ఇష్టం మేరకు చక్కగా సెట్ చేసుకోవచ్చు. ఇంకో ముఖ్య విషయం, స్క్రీన్ లాక్‌ అన్నది సాదా వచన రూపంలో నిల్వ చేయబడదు, కనుక ఖచ్చితమైన పాస్‌వర్డ్‌ ఏమిటనేది యాప్‌కు తెలియదు."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"నోటిఫికేషన్‌లను చూపండి"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"నోటిఫికేషన్‌లను చూపించడానికి యాప్‌ను అనుమతించండి"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"బయోమెట్రిక్ హార్డ్‌వేర్‌ని ఉపయోగించు"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ప్రమాణీకరణ కోసం బయోమెట్రిక్ హార్డ్‌వేర్‌ను ఉపయోగించడానికి యాప్‌ని అనుమతిస్తుంది"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"వేలిముద్ర హార్డ్‌వేర్‌ని నిర్వహించడానికి అనుమతి"</string>
diff --git a/core/res/res/values-ur-television/strings.xml b/core/res/res/values-ur-television/strings.xml
new file mode 100644
index 0000000..42f2085
--- /dev/null
+++ b/core/res/res/values-ur-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"مائیکروفون مسدود ہے"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"کیمرا مسدود ہے"</string>
+</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 5880de7..60f086b 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"فون کالز کریں اور ان کا نظم کریں"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"باڈی سینسرز"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"اپنی علامات حیات کے متعلق سنسر ڈیٹا تک رسائی حاصل کریں"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"اطلاعات"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"اطلاعات دکھائیں"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ونڈو مواد بازیافت کرنے کی"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"کسی ایسی ونڈو کے مواد کا معائنہ کریں جس کے ساتھ آپ تعامل کر رہے ہیں۔"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"ٹچ کے ذریعے دریافت کریں کو آن کرنے کی"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"ایپ کو کلیدی لاک اور کسی بھی متعلقہ پاس ورڈ سیکیورٹی کو غیر فعال کرنے کی اجازت دیتا ہے۔ مثلاً، کوئی آنے والی فون کال موصول ہونے کے وقت فون کلیدی لاک کو غیر فعال کرتا ہے، پھر کال پوری ہوجانے پر کلیدی لاک کو دوبارہ فعال کردیتا ہے۔"</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"اسکرین لاک کی پیچیدگی کی درخواست کریں"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"اپپ کو اسکرین لاک کی پیچیدگی (بہت زیادہ، درمیانی، کم یا کوئی بھی نہیں) کو جاننے کی اجازت دیتی ہے، جو طوالت کی ممکنہ حد اور اسکرین لاک کی قسم کو بتاتی ہے۔ اپپ صارفین کو یہ مشوره بھی دے سکتی ہے کہ وہ اسکرین لاک کو مخصوص لیول تک اپ ڈیٹ کریں لیکن صارفین آزادانہ طور پر نظر انداز اور نیویگیٹ کر سکتے ہیں۔ نوٹ کریں کہ اسکرین لاک پلین ٹیکسٹ میں اسٹور نہیں کیا جاتا ہے اس لیے اپپ کو صحیح پاس ورڈ نہیں معلوم ہوتا ہے۔"</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"اطلاعات دکھائیں"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"ایپ کو اطلاعات دکھانے کی اجازت دیتا ہے"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"بایومیٹرک ہارڈ ویئر استعمال کریں"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"ایپ کو توثیق کے لیے بایومیٹرک ہارڈ ویئر استعمال کرنے کی اجازت دیتا ہے"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"فنگر پرنٹ ہارڈ ویئر کا نظم کریں"</string>
diff --git a/core/res/res/values-zh-rCN-television/strings.xml b/core/res/res/values-zh-rCN-television/strings.xml
new file mode 100644
index 0000000..b30c9df
--- /dev/null
+++ b/core/res/res/values-zh-rCN-television/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="sensor_privacy_start_use_mic_notification_content_title" msgid="7002619958660406548">"麦克风已被屏蔽"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content_title" msgid="2131954635322568179">"摄像头已被屏蔽"</string>
+</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index ad9c34a..2e3c20b 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -326,10 +326,8 @@
     <string name="permgroupdesc_phone" msgid="270048070781478204">"yenza uphinde uphathe amakholi wefoni"</string>
     <string name="permgrouplab_sensors" msgid="9134046949784064495">"Izinzwa zomzimba"</string>
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"finyelela idatha yesizweli mayelana nezimpawu zakho ezibalulekile"</string>
-    <!-- no translation found for permgrouplab_notifications (5472972361980668884) -->
-    <skip />
-    <!-- no translation found for permgroupdesc_notifications (4608679556801506580) -->
-    <skip />
+    <string name="permgrouplab_notifications" msgid="5472972361980668884">"Izaziso"</string>
+    <string name="permgroupdesc_notifications" msgid="4608679556801506580">"bonisa izaziso"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Thola okuqukethwe kwewindi"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Hlola okuqukethwe kwewindi ohlanganyela nalo."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Vula ukuhlola ngokuthinta"</string>
@@ -553,10 +551,8 @@
     <string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Ivumela uhlelo lokusebenza ukukhubaza ukuvala ukhiye nanoma yikuphi ukuphepha kwephasiwedi okuhlobene. Isibonelo, ifoni ikhubaza ukuvala ukhiye lapho ithola ikholi yefoni engenayo, bese inike amandla kabusha ukuvala ukhiye lapho ikholi isiqedile."</string>
     <string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"cela ubunkimbinkimbi kokukhiya isikrini"</string>
     <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Ivumela uhlelo lokusebenza ukuthi lifunde ileveli yobunkimbinkimbi bokukhiya isikrini (okuphezulu, okuphakathi nendawo, okuphansi noma lutho), obubonisa ibanga elingaba khona lobude nohlobo lokukhiya isikrini Uhlelo lokusebenza lungaphinda luphakamise kubasebenzisi ukuthi babuyekeze ukukhiya isikrini kuleveli elithile kodwa abasebenzisi bangaziba ngokukhululekile baphinde bazulazule nje. Qaphela ukuthi ukukhiya isikrini akugcinwa kumbhalo osobala ukuze uhlelo lokusebenza lungayazi iphasiwedi enembile."</string>
-    <!-- no translation found for permlab_postNotification (4875401198597803658) -->
-    <skip />
-    <!-- no translation found for permdesc_postNotification (5974977162462877075) -->
-    <skip />
+    <string name="permlab_postNotification" msgid="4875401198597803658">"bonisa izaziso"</string>
+    <string name="permdesc_postNotification" msgid="5974977162462877075">"Kuvumela i-app ibonise izaziso"</string>
     <string name="permlab_useBiometric" msgid="6314741124749633786">"sebenzisa izingxenyekazi zekhompyutha ze-biometric"</string>
     <string name="permdesc_useBiometric" msgid="7502858732677143410">"Ivumela uhlelo lokusebenza ukuthi lusebenzise izingxenyekazi zekhompyutha ze-biometric ukuze kuqinisekiswe"</string>
     <string name="permlab_manageFingerprint" msgid="7432667156322821178">"phatha izingxenyekazi zekhompyutha zezigxivizo zeminwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 676546b..765495a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8372,7 +8372,7 @@
         <attr name="supportsAmbientMode" format="boolean" />
 
         <!-- Indicates that this wallpaper service should receive zoom transition updates when
-             changing the display state of the device (e.g. when folding or unfolding
+             changing the structural state of the device (e.g. when folding or unfolding
              a foldable device). When this value is set to true
              {@link android.service.wallpaper.WallpaperService.Engine} could receive zoom updates
              before or after changing the device state. Wallpapers receive zoom updates using
@@ -8381,8 +8381,8 @@
              {@link android.service.wallpaper.WallpaperService.Engine} is created and not destroyed.
              Default value is true.
              Corresponds to
-             {@link android.app.WallpaperInfo#shouldUseDefaultDisplayStateChangeTransition()} -->
-        <attr name="shouldUseDefaultDisplayStateChangeTransition" format="boolean" />
+             {@link android.app.WallpaperInfo#shouldUseDefaultUnfoldTransition()} -->
+        <attr name="shouldUseDefaultUnfoldTransition" format="boolean" />
 
         <!-- Uri that specifies a settings Slice for this wallpaper. -->
         <attr name="settingsSliceUri" format="string"/>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fea3a6e..a6e42bd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3222,7 +3222,7 @@
   <eat-comment />
 
   <staging-public-group type="attr" first-id="0x01ff0000">
-    <public name="shouldUseDefaultDisplayStateChangeTransition" />
+    <public name="shouldUseDefaultUnfoldTransition" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01fe0000">
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 166edca..d310736 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -27,9 +27,33 @@
        are totally dependent on the platform and can vary
        significantly, so should be measured on the shipping platform
        with a power meter. -->
-  <item name="ambient.on">0.1</item>  <!-- ~100mA -->
-  <item name="screen.on">0.1</item>  <!-- ~100mA -->
-  <item name="screen.full">0.1</item>  <!-- ~100mA -->
+
+  <!-- Display related values. -->
+  <!-- Average battery current draw of display0 while in ambient mode, including backlight.
+       There must be one of these for each display, labeled:
+       ambient.on.display0, ambient.on.display1, etc...
+
+       Each display suffix number should match it's ordinal in its display device config.
+  -->
+  <item name="ambient.on.display0">0.1</item>  <!-- ~100mA -->
+  <!-- Average battery current draw of display0 while on without backlight.
+       There must be one of these for each display, labeled:
+       screen.on.display0, screen.on.display1, etc...
+
+       Each display suffix number should match it's ordinal in its display device config.
+  -->
+  <item name="screen.on.display0">0.1</item>  <!-- ~100mA -->
+  <!-- Average battery current draw of the backlight at full brightness.
+       The full current draw of display N at full brightness should be the sum of screen.on.displayN
+       and screen.full.displayN
+
+       There must be one of these for each display, labeled:
+       screen.full.display0, screen.full.display1, etc...
+
+       Each display suffix number should match it's ordinal in its display device config.
+  -->
+  <item name="screen.full.display0">0.1</item>  <!-- ~100mA -->
+
   <item name="bluetooth.active">0.1</item> <!-- Bluetooth data transfer, ~10mA -->
   <item name="bluetooth.on">0.1</item>  <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->
   <item name="wifi.on">0.1</item>  <!-- ~3mA -->
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 9d2cab3..ddd0070 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -47,6 +47,7 @@
 
     @After
     public void tearDown() throws Exception {
+        BaseBundle.setShouldDefuse(false);
         if (mWtfHandler != null) {
             Log.setWtfHandler(mWtfHandler);
         }
@@ -355,6 +356,81 @@
         assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class);
     }
 
+    @Test
+    public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception {
+        Bundle.setShouldDefuse(false);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+        bundle.readFromParcel(getParcelledBundle(bundle));
+
+        // Default class-loader is the bootpath class-loader, which doesn't contain
+        // CustomParcelable, so trying to read it will throw BadParcelableException.
+        assertThrows(BadParcelableException.class, () -> bundle.getParcelable("key"));
+    }
+
+    @Test
+    public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception {
+        Bundle.setShouldDefuse(true);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+        bundle.putString("string", "value");
+        bundle.readFromParcel(getParcelledBundle(bundle));
+
+        // Default class-loader is the bootpath class-loader, which doesn't contain
+        // CustomParcelable, so trying to read it will throw BadParcelableException.
+        assertThat(bundle.<Parcelable>getParcelable("key")).isNull();
+        // Doesn't affect other items
+        assertThat(bundle.getString("string")).isEqualTo("value");
+    }
+
+    @Test
+    public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception {
+        Bundle.setShouldDefuse(true);
+        Bundle bundle = new Bundle();
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        bundle.putParcelable("key", parcelable);
+        bundle.putString("string", "value");
+        bundle.readFromParcel(getParcelledBundle(bundle));
+        assertThat(bundle.<Parcelable>getParcelable("key")).isNull();
+
+        // Now, we simulate reserializing and assign the proper class loader to not throw anymore
+        bundle.readFromParcel(getParcelledBundle(bundle));
+        bundle.setClassLoader(getClass().getClassLoader());
+
+        // We're able to retrieve it even though we failed before
+        assertThat(bundle.<Parcelable>getParcelable("key")).isEqualTo(parcelable);
+    }
+
+    @Test
+    public void partialDeserialization_whenNotDefusing_throws() throws Exception {
+        Bundle.setShouldDefuse(false);
+        Bundle bundle = getMalformedBundle();
+        assertThrows(BadParcelableException.class, bundle::isEmpty);
+    }
+
+    @Test
+    public void partialDeserialization_whenDefusing_emptiesMap() throws Exception {
+        Bundle.setShouldDefuse(true);
+        Bundle bundle = getMalformedBundle();
+        bundle.isEmpty();
+        // Nothing thrown
+        assertThat(bundle.size()).isEqualTo(0);
+    }
+
+    private Bundle getMalformedBundle() {
+        Parcel p = Parcel.obtain();
+        p.writeInt(BaseBundle.BUNDLE_MAGIC);
+        int start = p.dataPosition();
+        p.writeInt(1); // Number of items
+        p.writeString("key");
+        p.writeInt(131313); // Invalid type
+        p.writeInt(0); // Anything, really
+        int end = p.dataPosition();
+        p.setDataPosition(0);
+        return new Bundle(p, end - start);
+    }
+
+
     private static class CustomParcelable implements Parcelable {
         public final int integer;
         public final String string;
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index e7b88c8..469be4c 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -21,16 +21,19 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -60,6 +63,7 @@
 
     private ScrollCaptureTarget mTarget;
     private ScrollCaptureConnection mConnection;
+    private IBinder mConnectionBinder = new Binder("ScrollCaptureConnection Test");
 
     private Handler mHandler;
 
@@ -76,6 +80,7 @@
         mHandler = new Handler(getTargetContext().getMainLooper());
         when(mSurface.isValid()).thenReturn(true);
         when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
+        when(mRemote.asBinder()).thenReturn(mConnectionBinder);
 
         mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback);
         mTarget.setScrollBounds(mScrollBounds);
@@ -107,12 +112,13 @@
     @Test
     public void testStartCapture() throws Exception {
         mConnection.startCapture(mSurface, mRemote);
+        assertTrue(mConnection.isConnected());
+        assertFalse(mConnection.isActive());
 
         mCallback.completeStartRequest();
         assertTrue(mConnection.isActive());
 
         verify(mRemote, times(1)).onCaptureStarted();
-        verifyNoMoreInteractions(mRemote);
     }
 
     @Test
@@ -123,7 +129,7 @@
         mCallback.completeStartRequest();
         assertFalse(mConnection.isActive());
 
-        verifyNoMoreInteractions(mRemote);
+        verify(mRemote, never()).onCaptureStarted();
     }
 
     /** @see ScrollCaptureConnection#requestImage(Rect) */
@@ -131,42 +137,63 @@
     public void testRequestImage() throws Exception {
         mConnection.startCapture(mSurface, mRemote);
         mCallback.completeStartRequest();
-        reset(mRemote);
 
         mConnection.requestImage(new Rect(1, 2, 3, 4));
         mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
 
         verify(mRemote, times(1))
                 .onImageRequestCompleted(eq(0), eq(new Rect(1, 2, 3, 4)));
-        verifyNoMoreInteractions(mRemote);
     }
 
     @Test
     public void testRequestImage_cancellation() throws Exception {
         mConnection.startCapture(mSurface, mRemote);
         mCallback.completeStartRequest();
-        reset(mRemote);
 
         ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4));
         signal.cancel();
         mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
 
-        verifyNoMoreInteractions(mRemote);
+        verify(mRemote, never()).onImageRequestCompleted(anyInt(), any(Rect.class));
     }
 
     /** @see ScrollCaptureConnection#endCapture() */
     @Test
     public void testEndCapture() throws Exception {
         mConnection.startCapture(mSurface, mRemote);
+        assertTrue(mConnection.isConnected());
+
         mCallback.completeStartRequest();
-        reset(mRemote);
+        assertTrue(mConnection.isActive());
 
         mConnection.endCapture();
         mCallback.completeEndRequest();
+        assertFalse(mConnection.isActive());
 
         // And the reply is sent
         verify(mRemote, times(1)).onCaptureEnded();
-        verifyNoMoreInteractions(mRemote);
+
+        assertFalse(mConnection.isConnected());
+    }
+
+    /** @see ScrollCaptureConnection#endCapture() */
+    @Test
+    public void testClose_withPendingOperation() throws Exception {
+        mConnection.startCapture(mSurface, mRemote);
+        mCallback.completeStartRequest();
+
+        mConnection.requestImage(new Rect(1, 2, 3, 4));
+        assertFalse(mCallback.getLastCancellationSignal().isCanceled());
+
+        mConnection.close();
+
+        // And the reply is sent
+        assertTrue(mCallback.onScrollCaptureEndCalled());
+        assertTrue(mCallback.getLastCancellationSignal().isCanceled());
+        assertFalse(mConnection.isActive());
+        assertFalse(mConnection.isConnected());
+        verify(mRemote, never()).onCaptureEnded();
+        verify(mRemote, never()).onImageRequestCompleted(anyInt(), any(Rect.class));
     }
 
     /** @see ScrollCaptureConnection#endCapture() */
@@ -174,20 +201,19 @@
     public void testEndCapture_cancellation() throws Exception {
         mConnection.startCapture(mSurface, mRemote);
         mCallback.completeStartRequest();
-        reset(mRemote);
 
         ICancellationSignal signal = mConnection.endCapture();
         signal.cancel();
         mCallback.completeEndRequest();
 
-        verifyNoMoreInteractions(mRemote);
+        verify(mRemote, never()).onCaptureEnded();
     }
 
     @Test
     public void testClose() {
         mConnection.close();
         assertFalse(mConnection.isActive());
-        verifyNoMoreInteractions(mRemote);
+        assertFalse(mConnection.isConnected());
     }
 
     @Test
@@ -196,9 +222,11 @@
 
         mCallback.completeStartRequest();
         assertTrue(mConnection.isActive());
+        assertTrue(mConnection.isConnected());
 
         mConnection.close();
         mCallback.completeEndRequest();
         assertFalse(mConnection.isActive());
+        assertFalse(mConnection.isConnected());
     }
 }
diff --git a/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java
index 1520c6e..02921e7 100644
--- a/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java
+++ b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java
@@ -31,10 +31,13 @@
     private Consumer<Rect> mImageOnComplete;
     private Runnable mOnEndReady;
     private volatile int mModCount;
+    private boolean mOnScrollCaptureEndCalled;
+    private CancellationSignal mLastCancellationSignal;
 
     @Override
     public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
             @NonNull Consumer<Rect> onReady) {
+        mLastCancellationSignal = signal;
         mSearchConsumer = onReady;
         mModCount++;
     }
@@ -42,6 +45,7 @@
     @Override
     public void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
             @NonNull CancellationSignal signal, @NonNull Runnable onReady) {
+        mLastCancellationSignal = signal;
         mStartOnReady = onReady;
         mModCount++;
     }
@@ -50,6 +54,7 @@
     public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
             @NonNull CancellationSignal signal, @NonNull Rect captureArea,
             @NonNull Consumer<Rect> onComplete) {
+        mLastCancellationSignal = signal;
         mImageOnComplete = onComplete;
         mModCount++;
     }
@@ -57,8 +62,12 @@
     @Override
     public void onScrollCaptureEnd(@NonNull Runnable onReady) {
         mOnEndReady = onReady;
+        mOnScrollCaptureEndCalled = true;
     }
 
+    public boolean onScrollCaptureEndCalled() {
+        return mOnScrollCaptureEndCalled;
+    }
     void completeSearchRequest(Rect scrollBounds) {
         assertNotNull("Did not receive search request", mSearchConsumer);
         mSearchConsumer.accept(scrollBounds);
@@ -71,16 +80,26 @@
 
     void completeStartRequest() {
         assertNotNull("Did not receive start request", mStartOnReady);
-        mStartOnReady.run();
+        if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
+            mStartOnReady.run();
+        }
     }
 
     void completeImageRequest(Rect captured) {
         assertNotNull("Did not receive image request", mImageOnComplete);
-        mImageOnComplete.accept(captured);
+        if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
+            mImageOnComplete.accept(captured);
+        }
     }
 
     void completeEndRequest() {
         assertNotNull("Did not receive end request", mOnEndReady);
-        mOnEndReady.run();
+        if (mLastCancellationSignal != null && !mLastCancellationSignal.isCanceled()) {
+            mOnEndReady.run();
+        }
+    }
+
+    public CancellationSignal getLastCancellationSignal() {
+        return mLastCancellationSignal;
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index 79f7a5c..d76037e 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
@@ -36,7 +38,7 @@
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY, 10.0);
+            .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0);
 
     @Test
     public void testMeasuredEnergyBasedModel() {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index e7fa656..1bb6792 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -110,6 +110,14 @@
         return this;
     }
 
+    public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal,
+            double value) {
+        when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value);
+        when(mPowerProfile.getAveragePowerForOrdinal(eq(group), eq(ordinal),
+                anyDouble())).thenReturn(value);
+        return this;
+    }
+
     /** Call only after setting the power profile information. */
     public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
         return initMeasuredEnergyStatsLocked(new String[0]);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 5862368..88ee405 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -17,6 +17,10 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -53,7 +57,12 @@
         assertEquals(4, mProfile.getNumSpeedStepsInCpuCluster(1));
         assertEquals(60.0, mProfile.getAveragePowerForCpuCore(1, 3));
         assertEquals(3000.0, mProfile.getBatteryCapacity());
-        assertEquals(0.5, mProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY));
+        assertEquals(0.5,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
+        assertEquals(100.0,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0));
+        assertEquals(800.0,
+                mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
         assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO));
         assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO));
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index c695fc9..50e0a15 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.ActivityManager;
@@ -42,8 +45,8 @@
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0)
-            .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0);
+            .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0, 36.0)
+            .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0);
 
     @Test
     public void testMeasuredEnergyBasedModel() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
index b24a22d..16f732f 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
@@ -22,6 +22,8 @@
 import android.system.keystore2.Domain;
 import android.system.keystore2.KeyDescriptor;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.security.Key;
 
 /**
@@ -46,7 +48,11 @@
     // We do not include this member in comparisons.
     private final KeyStoreSecurityLevel mSecurityLevel;
 
-    AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor,
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor,
             long keyId,
             @NonNull Authorization[] authorizations,
             @NonNull String algorithm,
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
index 4842984..0b3be32 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
@@ -23,7 +23,7 @@
 import android.system.keystore2.KeyMetadata;
 
 import java.security.PublicKey;
-import java.util.Objects;
+import java.util.Arrays;
 
 /**
  * {@link PublicKey} backed by Android Keystore.
@@ -62,8 +62,8 @@
         int result = 1;
 
         result = prime * result + super.hashCode();
-        result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode());
-        result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode());
+        result = prime * result + Arrays.hashCode(mCertificate);
+        result = prime * result + Arrays.hashCode(mCertificateChain);
 
         return result;
     }
@@ -83,7 +83,7 @@
          */
         final AndroidKeyStorePublicKey other = (AndroidKeyStorePublicKey) obj;
 
-        return Objects.equals(mCertificate, other.mCertificate) && Objects.equals(mCertificateChain,
+        return Arrays.equals(mCertificate, other.mCertificate) && Arrays.equals(mCertificateChain,
                 other.mCertificateChain);
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 67358c4..33411e1 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -601,8 +601,6 @@
         }
         KeyProtection params = (KeyProtection) param;
 
-        @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX :
-                SecurityLevel.TRUSTED_ENVIRONMENT;
         @Domain int targetDomain = (getTargetDomain());
 
         if (key instanceof AndroidKeyStoreSecretKey) {
@@ -794,6 +792,9 @@
             flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING;
         }
 
+        @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX :
+                SecurityLevel.TRUSTED_ENVIRONMENT;
+
         try {
             KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel(
                     securityLevel);
diff --git a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
index 1bd3069..f96c39c8 100644
--- a/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
+++ b/keystore/tests/src/android/security/keystore2/AndroidKeyStoreSpiTest.java
@@ -24,7 +24,13 @@
 
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
+import android.security.KeyStoreSecurityLevel;
+import android.system.keystore2.Authorization;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
+import android.system.keystore2.KeyMetadata;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -52,4 +58,112 @@
         verify(mKeystore2).list(anyInt(), anyLong());
     }
 
+    @Mock
+    private KeyStoreSecurityLevel mKeystoreSecurityLevel;
+
+    private static KeyDescriptor descriptor() {
+        final KeyDescriptor keyDescriptor = new KeyDescriptor();
+        keyDescriptor.alias = "key";
+        keyDescriptor.blob = null;
+        keyDescriptor.domain = Domain.APP;
+        keyDescriptor.nspace = -1;
+        return keyDescriptor;
+    }
+
+    private static KeyMetadata metadata(byte[] cert, byte[] certChain) {
+        KeyMetadata metadata = new KeyMetadata();
+        metadata.authorizations = new Authorization[0];
+        metadata.certificate = cert;
+        metadata.certificateChain = certChain;
+        metadata.key = descriptor();
+        metadata.modificationTimeMs = 0;
+        metadata.keySecurityLevel = 1;
+        return metadata;
+    }
+
+    private static byte[] bytes(String string) {
+        return string.getBytes();
+    }
+
+    class MyPublicKey extends AndroidKeyStorePublicKey {
+        MyPublicKey(String cert, String chain, KeyStoreSecurityLevel securityLevel) {
+            super(descriptor(), metadata(cert.getBytes(), chain.getBytes()), "N/A".getBytes(),
+                    "RSA", securityLevel);
+        }
+
+        @Override
+        AndroidKeyStorePrivateKey getPrivateKey() {
+            return null;
+        }
+    }
+
+    private AndroidKeyStorePublicKey makePrivateKeyObject(String cert, String chain) {
+        return new MyPublicKey(cert, chain, mKeystoreSecurityLevel);
+    }
+
+    @Test
+    public void testKeystoreKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception {
+        AndroidKeyStoreKey key1 = new AndroidKeyStoreKey(descriptor(), 1, new Authorization[0],
+                "RSA", mKeystoreSecurityLevel);
+        AndroidKeyStoreKey key2 = new AndroidKeyStoreKey(descriptor(), 2, new Authorization[0],
+                "RSA", mKeystoreSecurityLevel);
+        AndroidKeyStoreKey key1_clone = new AndroidKeyStoreKey(descriptor(), 1,
+                new Authorization[0], "RSA", mKeystoreSecurityLevel);
+
+        assertThat("Identity should yield true", key1.equals(key1));
+        Assert.assertEquals("Identity should yield same hash codes",
+                key1.hashCode(), key1.hashCode());
+        assertThat("Identity should yield true", key2.equals(key2));
+        Assert.assertEquals("Identity should yield same hash codes",
+                key2.hashCode(), key2.hashCode());
+        assertThat("Different keys should differ", !key1.equals(key2));
+        Assert.assertNotEquals("Different keys should have different hash codes",
+                key1.hashCode(), key2.hashCode());
+
+        assertThat("Same keys should yield true", key1.equals(key1_clone));
+        assertThat("Same keys should yield true", key1_clone.equals(key1));
+        Assert.assertEquals("Same keys should yield same hash codes",
+                key1.hashCode(), key1_clone.hashCode());
+
+        assertThat("anything.equal(null) should yield false", !key1.equals(null));
+        assertThat("anything.equal(null) should yield false", !key2.equals(null));
+        assertThat("anything.equal(null) should yield false", !key1_clone.equals(null));
+    }
+
+    @Test
+    public void testKeystorePublicKeysAdhereToContractBetweenEqualsAndHashCode() throws Exception {
+        AndroidKeyStorePublicKey key1 = makePrivateKeyObject("myCert1", "myChain1");
+        AndroidKeyStorePublicKey key2 = makePrivateKeyObject("myCert2", "myChain1");
+        AndroidKeyStorePublicKey key3 = makePrivateKeyObject("myCert1", "myChain3");
+        AndroidKeyStorePublicKey key1_clone = makePrivateKeyObject("myCert1", "myChain1");
+
+        assertThat("Identity should yield true", key1.equals(key1));
+        Assert.assertEquals("Identity should yield same hash codes",
+                key1.hashCode(), key1.hashCode());
+        assertThat("Identity should yield true", key2.equals(key2));
+        Assert.assertEquals("Identity should yield same hash codes",
+                key2.hashCode(), key2.hashCode());
+        assertThat("Identity should yield true", key3.equals(key3));
+        Assert.assertEquals("Identity should yield same hash codes",
+                key3.hashCode(), key3.hashCode());
+        assertThat("Different keys should differ", !key1.equals(key2));
+        Assert.assertNotEquals("Different keys should have different hash codes",
+                key1.hashCode(), key2.hashCode());
+        assertThat("Different keys should differ", !key1.equals(key3));
+        Assert.assertNotEquals("Different keys should have different hash codes",
+                key1.hashCode(), key3.hashCode());
+        assertThat("Different keys should differ", !key2.equals(key3));
+        Assert.assertNotEquals("Different keys should have different hash codes",
+                key2.hashCode(), key3.hashCode());
+
+        assertThat("Same keys should yield true", key1.equals(key1_clone));
+        assertThat("Same keys should yield true", key1_clone.equals(key1));
+        Assert.assertEquals("Same keys should yield same hash codes",
+                key1.hashCode(), key1_clone.hashCode());
+
+        assertThat("anything.equal(null) should yield false", !key1.equals(null));
+        assertThat("anything.equal(null) should yield false", !key2.equals(null));
+        assertThat("anything.equal(null) should yield false", !key3.equals(null));
+        assertThat("anything.equal(null) should yield false", !key1_clone.equals(null));
+    }
 }
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 9aaef3b..3ba1a34 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,6 +39,14 @@
 }
 
 filegroup {
+    name: "wm_shell_util-sources",
+    srcs: [
+        "src/com/android/wm/shell/util/**/*.java",
+    ],
+    path: "src",
+}
+
+filegroup {
     name: "wm_shell-aidls",
     srcs: [
         "src/**/*.aidl",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index d925a92..020ecb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -42,6 +42,7 @@
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizerController;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
 
@@ -322,10 +323,9 @@
     }
 
     @Override
-    public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-            boolean playRevealAnimation) {
+    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
         if (mStartingWindow != null) {
-            mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+            mStartingWindow.removeStartingWindow(removalInfo);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index b48bda3..bef26bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -558,6 +558,8 @@
         }
         Bubble bubbleToRemove = mBubbles.get(indexToRemove);
         bubbleToRemove.stopInflation();
+        overflowBubble(reason, bubbleToRemove);
+
         if (mBubbles.size() == 1) {
             if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
                 // No more active bubbles but we have stuff in the overflow -- select that view
@@ -581,8 +583,6 @@
             mStateChange.orderChanged |= repackAll();
         }
 
-        overflowBubble(reason, bubbleToRemove);
-
         // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
         if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
             // Move selection to the new bubble at the same position.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 97c89d0..33ce383 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -335,9 +334,6 @@
                 boolean alwaysConsumeSystemBars, int displayId) {}
 
         @Override
-        public void locationInParentDisplayChanged(Point offset) {}
-
-        @Override
         public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {}
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
index 08ab85c..fc1b704 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
@@ -16,9 +16,6 @@
 
 package com.android.wm.shell.fullscreen;
 
-import static android.graphics.Color.blue;
-import static android.graphics.Color.green;
-import static android.graphics.Color.red;
 import static android.util.MathUtils.lerp;
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -36,12 +33,11 @@
 import android.view.SurfaceControl;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.wm.shell.R;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
 
 import java.util.concurrent.Executor;
 
@@ -59,21 +55,17 @@
     private static final float VERTICAL_START_MARGIN = 0.03f;
     private static final float END_SCALE = 1f;
     private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
-    private static final int BACKGROUND_LAYER_Z_INDEX = -1;
 
-    private final Context mContext;
     private final Executor mExecutor;
     private final ShellUnfoldProgressProvider mProgressProvider;
-    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final DisplayInsetsController mDisplayInsetsController;
 
     private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+    private final UnfoldBackgroundController mBackgroundController;
 
-    private SurfaceControl mBackgroundLayer;
     private InsetsSource mTaskbarInsetsSource;
 
     private final float mWindowCornerRadiusPx;
-    private final float[] mBackgroundColor;
     private final float mExpandedTaskBarHeight;
 
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -81,19 +73,17 @@
     public FullscreenUnfoldController(
             @NonNull Context context,
             @NonNull Executor executor,
+            @NonNull UnfoldBackgroundController backgroundController,
             @NonNull ShellUnfoldProgressProvider progressProvider,
-            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @NonNull DisplayInsetsController displayInsetsController
     ) {
-        mContext = context;
         mExecutor = executor;
-        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mProgressProvider = progressProvider;
         mDisplayInsetsController = displayInsetsController;
         mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.taskbar_frame_height);
-        mBackgroundColor = getBackgroundColor();
+        mBackgroundController = backgroundController;
     }
 
     /**
@@ -108,7 +98,7 @@
     public void onStateChangeProgress(float progress) {
         if (mAnimationContextByTaskId.size() == 0) return;
 
-        ensureBackground();
+        mBackgroundController.ensureBackground(mTransaction);
 
         for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
             final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
@@ -135,7 +125,7 @@
             resetSurface(context);
         }
 
-        removeBackground();
+        mBackgroundController.removeBackground(mTransaction);
         mTransaction.apply();
     }
 
@@ -178,7 +168,7 @@
         }
 
         if (mAnimationContextByTaskId.size() == 0) {
-            removeBackground();
+            mBackgroundController.removeBackground(mTransaction);
         }
 
         mTransaction.apply();
@@ -194,39 +184,6 @@
                         (float) context.mTaskInfo.positionInParent.y);
     }
 
-    private void ensureBackground() {
-        if (mBackgroundLayer != null) return;
-
-        SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
-                .setName("app-unfold-background")
-                .setCallsite("AppUnfoldTransitionController")
-                .setColorLayer();
-        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
-        mBackgroundLayer = colorLayerBuilder.build();
-
-        mTransaction
-                .setColor(mBackgroundLayer, mBackgroundColor)
-                .show(mBackgroundLayer)
-                .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
-    }
-
-    private void removeBackground() {
-        if (mBackgroundLayer == null) return;
-        if (mBackgroundLayer.isValid()) {
-            mTransaction.remove(mBackgroundLayer);
-        }
-        mBackgroundLayer = null;
-    }
-
-    private float[] getBackgroundColor() {
-        int colorInt = mContext.getResources().getColor(R.color.unfold_transition_background);
-        return new float[]{
-                (float) red(colorInt) / 255.0F,
-                (float) green(colorInt) / 255.0F,
-                (float) blue(colorInt) / 255.0F
-        };
-    }
-
     private class AnimationContext {
         final SurfaceControl mLeash;
         final Rect mStartCropRect = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 8e5c5c5..2e918f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -295,7 +295,7 @@
             ShellExecutor mainExecutor
     ) {
         // Ensure that we are the primary user's SystemUI.
-        final int processUser = UserManager.get(context).getUserHandle();
+        final int processUser = UserManager.get(context).getProcessUserId();
         if (processUser != UserHandle.USER_SYSTEM) {
             throw new IllegalStateException("Non-primary Pip component not currently supported.");
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index d0998eb..7f82ebd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
@@ -38,8 +39,10 @@
 
     MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
+            SurfaceSession surfaceSession,
+            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+                stageTaskUnfoldController);
     }
 
     boolean isActive() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 0e7ccd3..dc8fb9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -46,8 +46,10 @@
 
     SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession) {
-        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
+            SurfaceSession surfaceSession,
+            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+                stageTaskUnfoldController);
         mContext = context;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index ac68b3b..0d52719 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -68,8 +68,11 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 
+import javax.inject.Provider;
+
 /**
  * Class manages split-screen multitasking mode and implements the main interface
  * {@link SplitScreen}.
@@ -91,6 +94,7 @@
     private final Transitions mTransitions;
     private final TransactionPool mTransactionPool;
     private final SplitscreenEventLogger mLogger;
+    private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
 
     private StageCoordinator mStageCoordinator;
 
@@ -99,7 +103,8 @@
             RootTaskDisplayAreaOrganizer rootTDAOrganizer,
             ShellExecutor mainExecutor, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController,
-            Transitions transitions, TransactionPool transactionPool) {
+            Transitions transitions, TransactionPool transactionPool,
+            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mContext = context;
@@ -109,6 +114,7 @@
         mDisplayInsetsController = displayInsetsController;
         mTransitions = transitions;
         mTransactionPool = transactionPool;
+        mUnfoldControllerProvider = unfoldControllerProvider;
         mLogger = new SplitscreenEventLogger();
     }
 
@@ -131,7 +137,8 @@
             // TODO: Multi-display
             mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                     mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
-                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger);
+                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+                    mUnfoldControllerProvider);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d7a6cff..414b4e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -95,6 +95,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -121,8 +124,10 @@
 
     private final MainStage mMainStage;
     private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+    private final StageTaskUnfoldController mMainUnfoldController;
     private final SideStage mSideStage;
     private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+    private final StageTaskUnfoldController mSideUnfoldController;
     @SplitPosition
     private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
 
@@ -179,26 +184,32 @@
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool, SplitscreenEventLogger logger) {
+            TransactionPool transactionPool, SplitscreenEventLogger logger,
+            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
         mRootTDAOrganizer = rootTDAOrganizer;
         mTaskOrganizer = taskOrganizer;
         mLogger = logger;
+        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
         mMainStage = new MainStage(
                 mTaskOrganizer,
                 mDisplayId,
                 mMainStageListener,
                 mSyncQueue,
-                mSurfaceSession);
+                mSurfaceSession,
+                mMainUnfoldController);
         mSideStage = new SideStage(
                 mContext,
                 mTaskOrganizer,
                 mDisplayId,
                 mSideStageListener,
                 mSyncQueue,
-                mSurfaceSession);
+                mSurfaceSession,
+                mSideUnfoldController);
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
         mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
@@ -218,7 +229,8 @@
             MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool,
-            SplitscreenEventLogger logger) {
+            SplitscreenEventLogger logger,
+            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -232,6 +244,8 @@
         mSplitLayout = splitLayout;
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                 mOnTransitionAnimationComplete);
+        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
         mLogger = logger;
         transitions.addHandler(this);
     }
@@ -460,6 +474,7 @@
                 onLayoutChanged(mSplitLayout);
             } else {
                 updateWindowBounds(mSplitLayout, wct);
+                updateUnfoldBounds();
             }
         }
     }
@@ -515,6 +530,9 @@
         mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
         mMainStage.deactivate(wct, childrenToTop == mMainStage);
         mTaskOrganizer.applyTransaction(wct);
+        mSyncQueue.runInSync(t -> t
+                .setWindowCrop(mMainStage.mRootLeash, null)
+                .setWindowCrop(mSideStage.mRootLeash, null));
         // Hide divider and reset its position.
         setDividerVisibility(false);
         mSplitLayout.resetDividerPosition();
@@ -603,6 +621,11 @@
             final SplitScreen.SplitScreenListener l = mListeners.get(i);
             l.onSplitVisibilityChanged(mDividerVisible);
         }
+
+        if (mMainUnfoldController != null && mSideUnfoldController != null) {
+            mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+            mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+        }
     }
 
     private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
@@ -641,6 +664,7 @@
         mDividerVisible = visible;
         if (visible) {
             mSplitLayout.init();
+            updateUnfoldBounds();
         } else {
             mSplitLayout.release();
         }
@@ -784,12 +808,20 @@
     public void onLayoutChanged(SplitLayout layout) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
+        updateUnfoldBounds();
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
         mSideStage.setOutlineVisibility(true);
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
+    private void updateUnfoldBounds() {
+        if (mMainUnfoldController != null && mSideUnfoldController != null) {
+            mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+            mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+        }
+    }
+
     /**
      * Populates `wct` with operations that match the split windows to the current layout.
      * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
@@ -846,6 +878,11 @@
                     mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
                     mDisplayImeController, mTaskOrganizer);
             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+            if (mMainUnfoldController != null && mSideUnfoldController != null) {
+                mMainUnfoldController.init();
+                mSideUnfoldController.init();
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 4140332..84d570f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -24,6 +24,7 @@
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.annotation.CallSuper;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -80,12 +81,16 @@
     protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
     private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
 
+    private final StageTaskUnfoldController mStageTaskUnfoldController;
+
     StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession) {
+            SurfaceSession surfaceSession,
+            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
         mCallbacks = callbacks;
         mSyncQueue = syncQueue;
         mSurfaceSession = surfaceSession;
+        mStageTaskUnfoldController = stageTaskUnfoldController;
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
     }
 
@@ -158,6 +163,10 @@
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
         }
+
+        if (mStageTaskUnfoldController != null) {
+            mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+        }
     }
 
     @Override
@@ -210,6 +219,10 @@
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
         }
+
+        if (mStageTaskUnfoldController != null) {
+            mStageTaskUnfoldController.onTaskVanished(taskInfo);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
new file mode 100644
index 0000000..e904f6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * 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.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+    private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+    private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+    private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+    private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+    private final DisplayInsetsController mDisplayInsetsController;
+    private final UnfoldBackgroundController mBackgroundController;
+    private final Executor mExecutor;
+    private final int mExpandedTaskBarHeight;
+    private final float mWindowCornerRadiusPx;
+    private final Rect mStageBounds = new Rect();
+    private final TransactionPool mTransactionPool;
+
+    private InsetsSource mTaskbarInsetsSource;
+    private boolean mBothStagesVisible;
+
+    public StageTaskUnfoldController(@NonNull Context context,
+            @NonNull TransactionPool transactionPool,
+            @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+            @NonNull DisplayInsetsController displayInsetsController,
+            @NonNull UnfoldBackgroundController backgroundController,
+            @NonNull Executor executor) {
+        mUnfoldProgressProvider = unfoldProgressProvider;
+        mTransactionPool = transactionPool;
+        mExecutor = executor;
+        mBackgroundController = backgroundController;
+        mDisplayInsetsController = displayInsetsController;
+        mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.taskbar_frame_height);
+    }
+
+    /**
+     * Initializes the controller, starts listening for the external events
+     */
+    public void init() {
+        mUnfoldProgressProvider.addListener(mExecutor, this);
+        mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+    }
+
+    @Override
+    public void insetsChanged(InsetsState insetsState) {
+        mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+            context.update();
+        }
+    }
+
+    /**
+     * Called when split screen task appeared
+     * @param taskInfo info for the appeared task
+     * @param leash surface leash for the appeared task
+     */
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+        AnimationContext context = new AnimationContext(leash);
+        mAnimationContextByTaskId.put(taskInfo.taskId, context);
+    }
+
+    /**
+     * Called when a split screen task vanished
+     * @param taskInfo info for the vanished task
+     */
+    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+        if (context != null) {
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+            resetSurface(transaction, context);
+            transaction.apply();
+            mTransactionPool.release(transaction);
+        }
+        mAnimationContextByTaskId.remove(taskInfo.taskId);
+    }
+
+    @Override
+    public void onStateChangeProgress(float progress) {
+        if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+        mBackgroundController.ensureBackground(transaction);
+
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+            context.mCurrentCropRect.set(RECT_EVALUATOR
+                    .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+            transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+                    .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+        }
+
+        transaction.apply();
+
+        mTransactionPool.release(transaction);
+    }
+
+    @Override
+    public void onStateChangeFinished() {
+        resetTransformations();
+    }
+
+    /**
+     * Called when split screen visibility changes
+     * @param bothStagesVisible true if both stages of the split screen are visible
+     */
+    public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+        mBothStagesVisible = bothStagesVisible;
+        if (!bothStagesVisible) {
+            resetTransformations();
+        }
+    }
+
+    /**
+     * Called when split screen stage bounds changed
+     * @param bounds new bounds for this stage
+     */
+    public void onLayoutChanged(Rect bounds) {
+        mStageBounds.set(bounds);
+
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+            context.update();
+        }
+    }
+
+    private void resetTransformations() {
+        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+            resetSurface(transaction, context);
+        }
+        mBackgroundController.removeBackground(transaction);
+        transaction.apply();
+
+        mTransactionPool.release(transaction);
+    }
+
+    private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+        transaction
+                .setWindowCrop(context.mLeash, null)
+                .setCornerRadius(context.mLeash, 0.0F);
+    }
+
+    private class AnimationContext {
+        final SurfaceControl mLeash;
+        final Rect mStartCropRect = new Rect();
+        final Rect mEndCropRect = new Rect();
+        final Rect mCurrentCropRect = new Rect();
+
+        private AnimationContext(SurfaceControl leash) {
+            this.mLeash = leash;
+            update();
+        }
+
+        private void update() {
+            mStartCropRect.set(mStageBounds);
+
+            if (mTaskbarInsetsSource != null) {
+                // Only insets the cropping window with taskbar when taskbar is expanded
+                if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+                    mStartCropRect.inset(mTaskbarInsetsSource
+                            .calculateVisibleInsets(mStartCropRect));
+                }
+            }
+
+            // Offset to surface coordinates as layout bounds are in screen coordinates
+            mStartCropRect.offsetTo(0, 0);
+
+            mEndCropRect.set(mStartCropRect);
+
+            int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+            int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+            mStartCropRect.inset(margin, margin, margin, margin);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ff3428c..979bf00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -37,7 +37,6 @@
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.RemoteCallback;
@@ -48,7 +47,6 @@
 import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.Display;
-import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.WindowManager;
@@ -58,6 +56,7 @@
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowInfo.StartingWindowType;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskSnapshot;
 
 import com.android.internal.R;
@@ -118,6 +117,7 @@
     private Choreographer mChoreographer;
     private final WindowManagerGlobal mWindowManagerGlobal;
     private StartingSurface.SysuiProxy mSysuiProxy;
+    private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
 
     /**
      * @param splashScreenExecutor The thread used to control add and remove starting window.
@@ -458,12 +458,13 @@
     /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
-    public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-            boolean playRevealAnimation) {
+    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
         if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
-            Slog.d(TAG, "Task start finish, remove starting surface for task " + taskId);
+            Slog.d(TAG, "Task start finish, remove starting surface for task "
+                    + removalInfo.taskId);
         }
-        removeWindowSynced(taskId, leash, frame, playRevealAnimation);
+        removeWindowSynced(removalInfo);
+
     }
 
     /**
@@ -556,7 +557,8 @@
     }
 
     private void removeWindowNoAnimate(int taskId) {
-        removeWindowSynced(taskId, null, null, false);
+        mTmpRemovalInfo.taskId = taskId;
+        removeWindowSynced(mTmpRemovalInfo);
     }
 
     void onImeDrawnOnTask(int taskId) {
@@ -568,8 +570,8 @@
         mStartingWindowRecords.remove(taskId);
     }
 
-    protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
-            boolean playRevealAnimation) {
+    protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) {
+        final int taskId = removalInfo.taskId;
         final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
         if (record != null) {
             if (record.mDecorView != null) {
@@ -580,9 +582,9 @@
                     if (record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                         removeWindowInner(record.mDecorView, false);
                     } else {
-                        if (playRevealAnimation) {
+                        if (removalInfo.playRevealAnimation) {
                             mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
-                                    leash, frame,
+                                    removalInfo.windowAnimationLeash, removalInfo.mainFrame,
                                     () -> removeWindowInner(record.mDecorView, true));
                         } else {
                             // the SplashScreenView has been copied to client, hide the view to skip
@@ -602,7 +604,7 @@
                     Slog.v(TAG, "Removing task snapshot window for " + taskId);
                 }
                 record.mTaskSnapshotWindow.scheduleRemove(
-                        () -> mStartingWindowRecords.remove(taskId));
+                        () -> mStartingWindowRecords.remove(taskId), removalInfo.deferRemoveForIme);
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 4433e27..99644f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -28,16 +28,14 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseIntArray;
-import android.view.SurfaceControl;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowInfo.StartingWindowType;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskOrganizer;
 import android.window.TaskSnapshot;
 
@@ -68,7 +66,7 @@
 public class StartingWindowController implements RemoteCallable<StartingWindowController> {
     private static final String TAG = StartingWindowController.class.getSimpleName();
 
-    public static final boolean DEBUG_SPLASH_SCREEN = Build.isDebuggable();
+    public static final boolean DEBUG_SPLASH_SCREEN = false;
     public static final boolean DEBUG_TASK_SNAPSHOT = false;
 
     private static final long TASK_BG_COLOR_RETAIN_TIME_MS = 5000;
@@ -186,13 +184,12 @@
     /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
-    public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-            boolean playRevealAnimation) {
+    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
         mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
-                taskId, leash, frame, playRevealAnimation));
+                removalInfo));
         mSplashScreenExecutor.executeDelayed(() -> {
             synchronized (mTaskBackgroundColors) {
-                mTaskBackgroundColors.delete(taskId);
+                mTaskBackgroundColors.delete(removalInfo.taskId);
             }
         }, TASK_BG_COLOR_RETAIN_TIME_MS);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 979aa1f..3e88c46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -123,14 +123,13 @@
      * Ideally the delay time will be shorter when receiving
      * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
      */
-    private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 450;
+    private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
 
     //tmp vars for unused relayout params
     private static final Point TMP_SURFACE_SIZE = new Point();
 
     private final Window mWindow;
     private final Runnable mClearWindowHandler;
-    private final long mDelayRemovalTime;
     private final ShellExecutor mSplashScreenExecutor;
     private final SurfaceControl mSurfaceControl;
     private final IWindowSession mSession;
@@ -221,13 +220,10 @@
             taskDescription.setBackgroundColor(WHITE);
         }
 
-        final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
-                : DELAY_REMOVAL_TIME_GENERAL;
-
         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
                 surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
                 windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
-                delayRemovalTime, topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
+                topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
         final Window window = snapshotSurface.mWindow;
 
         final InsetsState tmpInsetsState = new InsetsState();
@@ -265,9 +261,8 @@
     public TaskSnapshotWindow(SurfaceControl surfaceControl,
             TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
             int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
-            int currentOrientation, int activityType, long delayRemovalTime,
-            InsetsState topWindowInsetsState, Runnable clearWindowHandler,
-            ShellExecutor splashScreenExecutor) {
+            int currentOrientation, int activityType, InsetsState topWindowInsetsState,
+            Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
         mSplashScreenExecutor = splashScreenExecutor;
         mSession = WindowManagerGlobal.getWindowSession();
         mWindow = new Window();
@@ -283,7 +278,6 @@
         mStatusBarColor = taskDescription.getStatusBarColor();
         mOrientationOnCreation = currentOrientation;
         mActivityType = activityType;
-        mDelayRemovalTime = delayRemovalTime;
         mTransaction = new SurfaceControl.Transaction();
         mClearWindowHandler = clearWindowHandler;
         mHasImeSurface = snapshot.hasImeSurface();
@@ -314,7 +308,7 @@
         mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
     }
 
-    void scheduleRemove(Runnable onRemove) {
+    void scheduleRemove(Runnable onRemove, boolean deferRemoveForIme) {
         // Show the latest content as soon as possible for unlocking to home.
         if (mActivityType == ACTIVITY_TYPE_HOME) {
             removeImmediately();
@@ -329,9 +323,12 @@
             TaskSnapshotWindow.this.removeImmediately();
             onRemove.run();
         };
-        mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+        final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
+                ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+                : DELAY_REMOVAL_TIME_GENERAL;
+        mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
         if (DEBUG) {
-            Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+            Slog.d(TAG, "Defer removing snapshot surface in " + delayRemovalTime);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index b673d48..663d647 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -41,6 +41,7 @@
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.isIndependent;
 
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
@@ -70,6 +71,7 @@
 import android.view.animation.Transformation;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.R;
@@ -82,6 +84,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.CounterRotator;
 
 import java.util.ArrayList;
 
@@ -269,9 +272,16 @@
         final ArrayList<Animator> animations = new ArrayList<>();
         mAnimations.put(transition, animations);
 
+        final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>();
+
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
 
+            for (int i = 0; i < counterRotators.size(); ++i) {
+                counterRotators.valueAt(i).cleanUp(info.getRootLeash());
+            }
+            counterRotators.clear();
+
             if (mRotationAnimation != null) {
                 mRotationAnimation.kill();
                 mRotationAnimation = null;
@@ -285,16 +295,44 @@
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
 
-            if (info.getType() == TRANSIT_CHANGE && change.getMode() == TRANSIT_CHANGE
-                    && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
-                boolean isSeamless = isRotationSeamless(info, mDisplayController);
-                final int anim = getRotationAnimation(info);
-                if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
-                    mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                            mTransactionPool, startTransaction, change, info.getRootLeash());
-                    mRotationAnimation.startAnimation(animations, onAnimFinish,
-                            mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
-                    continue;
+            if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+                int rotateDelta = change.getEndRotation() - change.getStartRotation();
+                int displayW = change.getEndAbsBounds().width();
+                int displayH = change.getEndAbsBounds().height();
+                if (info.getType() == TRANSIT_CHANGE) {
+                    boolean isSeamless = isRotationSeamless(info, mDisplayController);
+                    final int anim = getRotationAnimation(info);
+                    if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
+                        mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
+                                mTransactionPool, startTransaction, change, info.getRootLeash());
+                        mRotationAnimation.startAnimation(animations, onAnimFinish,
+                                mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+                        continue;
+                    }
+                } else {
+                    // opening/closing an app into a new orientation. Counter-rotate all
+                    // "going-away" things since they are still in the old orientation.
+                    for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+                        final TransitionInfo.Change innerChange = info.getChanges().get(j);
+                        if (!Transitions.isClosingType(innerChange.getMode())
+                                || !isIndependent(innerChange, info)
+                                || innerChange.getParent() == null) {
+                            continue;
+                        }
+                        CounterRotator crot = counterRotators.get(innerChange.getParent());
+                        if (crot == null) {
+                            crot = new CounterRotator();
+                            crot.setup(startTransaction,
+                                    info.getChange(innerChange.getParent()).getLeash(),
+                                    rotateDelta, displayW, displayH);
+                            if (crot.getSurface() != null) {
+                                int layer = info.getChanges().size() - j;
+                                startTransaction.setLayer(crot.getSurface(), layer);
+                            }
+                            counterRotators.put(innerChange.getParent(), crot);
+                        }
+                        crot.addChild(startTransaction, innerChange.getLeash());
+                    }
                 }
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
new file mode 100644
index 0000000..9faf454
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.unfold;
+
+import static android.graphics.Color.blue;
+import static android.graphics.Color.green;
+import static android.graphics.Color.red;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background color layer for the unfold animations
+ */
+public class UnfoldBackgroundController {
+
+    private static final int BACKGROUND_LAYER_Z_INDEX = -1;
+
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+    private final float[] mBackgroundColor;
+    private SurfaceControl mBackgroundLayer;
+
+    public UnfoldBackgroundController(
+            @NonNull Context context,
+            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+        mBackgroundColor = getBackgroundColor(context);
+    }
+
+    /**
+     * Ensures that unfold animation background color layer is present,
+     * @param transaction where we should add the background if it is not added
+     */
+    public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundLayer != null) return;
+
+        SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+                .setName("app-unfold-background")
+                .setCallsite("AppUnfoldTransitionController")
+                .setColorLayer();
+        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+        mBackgroundLayer = colorLayerBuilder.build();
+
+        transaction
+                .setColor(mBackgroundLayer, mBackgroundColor)
+                .show(mBackgroundLayer)
+                .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
+    }
+
+    /**
+     * Ensures that the background is not visible
+     * @param transaction as part of which the removal will happen if needed
+     */
+    public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+        if (mBackgroundLayer == null) return;
+        if (mBackgroundLayer.isValid()) {
+            transaction.remove(mBackgroundLayer);
+        }
+        mBackgroundLayer = null;
+    }
+
+    private float[] getBackgroundColor(Context context) {
+        int colorInt = context.getResources().getColor(R.color.unfold_transition_background);
+        return new float[]{
+                (float) red(colorInt) / 255.0F,
+                (float) green(colorInt) / 255.0F,
+                (float) blue(colorInt) / 255.0F
+        };
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
new file mode 100644
index 0000000..b9b6716
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.util;
+
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class that takes care of counter-rotating surfaces during a transition animation.
+ */
+public class CounterRotator {
+    SurfaceControl mSurface = null;
+    ArrayList<SurfaceControl> mRotateChildren = null;
+
+    /** Gets the surface with the counter-rotation. */
+    public SurfaceControl getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Sets up this rotator.
+     *
+     * @param rotateDelta is the forward rotation change (the rotation the display is making).
+     * @param displayW (and H) Is the size of the rotating display.
+     */
+    public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
+            float displayW, float displayH) {
+        if (rotateDelta == 0) return;
+        mRotateChildren = new ArrayList<>();
+        // We want to counter-rotate, so subtract from 4
+        rotateDelta = 4 - (rotateDelta + 4) % 4;
+        mSurface = new SurfaceControl.Builder()
+                .setName("Transition Unrotate")
+                .setContainerLayer()
+                .setParent(parent)
+                .build();
+        // column-major
+        if (rotateDelta == 1) {
+            t.setMatrix(mSurface, 0, 1, -1, 0);
+            t.setPosition(mSurface, displayW, 0);
+        } else if (rotateDelta == 2) {
+            t.setMatrix(mSurface, -1, 0, 0, -1);
+            t.setPosition(mSurface, displayW, displayH);
+        } else if (rotateDelta == 3) {
+            t.setMatrix(mSurface, 0, -1, 1, 0);
+            t.setPosition(mSurface, 0, displayH);
+        }
+        t.show(mSurface);
+    }
+
+    /**
+     * Add a surface that needs to be counter-rotate.
+     */
+    public void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
+        if (mSurface == null) return;
+        t.reparent(child, mSurface);
+        mRotateChildren.add(child);
+    }
+
+    /**
+     * Clean-up. This undoes any reparenting and effectively stops the counter-rotation.
+     */
+    public void cleanUp(SurfaceControl rootLeash) {
+        if (mSurface == null) return;
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
+            t.reparent(mRotateChildren.get(i), rootLeash);
+        }
+        t.remove(mSurface);
+        t.apply();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index b0312e6..091022a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -793,7 +793,7 @@
     }
 
     @Test
-    public void test_expanded_removeLastBubble_collapsesStack() {
+    public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() {
         // Setup
         sendUpdatedEntryAtTime(mEntryA1, 1000);
         changeExpandedStateAtTime(true, 2000);
@@ -802,6 +802,21 @@
         // Test
         mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
+        assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0);
+        assertSelectionChangedTo(mBubbleData.getOverflow());
+    }
+
+    @Test
+    public void test_expanded_removeLastBubble_collapsesIfOverflowEmpty() {
+        // Setup
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        changeExpandedStateAtTime(true, 2000);
+        mBubbleData.setListener(mListener);
+
+        // Test
+        mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_BUBBLE_UP);
+        verifyUpdateReceived();
+        assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
         assertExpandedChangedTo(false);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 1bb5fd1..12b547a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -56,7 +56,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
         mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
-                mSurfaceSession);
+                mSurfaceSession, null);
         mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 3a2516e..838aa81 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -64,7 +64,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession);
+                mSyncQueue, mSurfaceSession, null);
         mSideStage.onTaskAppeared(mRootTask, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 736566e5..f90af23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -18,7 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
-
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
@@ -39,6 +38,10 @@
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
+
+import javax.inject.Provider;
+
 public class SplitTestUtils {
 
     static SplitLayout createMockSplitLayout() {
@@ -68,10 +71,11 @@
                 MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
-                SplitscreenEventLogger logger) {
+                SplitscreenEventLogger logger,
+                Provider<Optional<StageTaskUnfoldController>> unfoldController) {
             super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
                     sideStage, imeController, insetsController, splitLayout, transitions,
-                    transactionPool, logger);
+                    transactionPool, logger, unfoldController);
 
             // Prepare default TaskDisplayArea for testing.
             mDisplayAreaInfo = new DisplayAreaInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 8dce454..be103863 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -74,6 +74,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.util.Optional;
+
 /** Tests for {@link StageCoordinator} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -106,16 +108,16 @@
         doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
         mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
-                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
+                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
-                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
+                StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
                 mTransactionPool,
-                mLogger);
+                mLogger, Optional::empty);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
         doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
                 .when(mTransitions).startTransition(anyInt(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d930d4ca..a39d331 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -18,18 +18,20 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.graphics.Rect;
+import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -43,6 +45,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -51,29 +54,55 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-/** Tests for {@link StageCoordinator} */
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Tests for {@link StageCoordinator}
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class StageCoordinatorTests extends ShellTestCase {
-    @Mock private ShellTaskOrganizer mTaskOrganizer;
-    @Mock private SyncTransactionQueue mSyncQueue;
-    @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
-    @Mock private MainStage mMainStage;
-    @Mock private SideStage mSideStage;
-    @Mock private DisplayImeController mDisplayImeController;
-    @Mock private DisplayInsetsController mDisplayInsetsController;
-    @Mock private Transitions mTransitions;
-    @Mock private TransactionPool mTransactionPool;
-    @Mock private SplitscreenEventLogger mLogger;
+    @Mock
+    private ShellTaskOrganizer mTaskOrganizer;
+    @Mock
+    private SyncTransactionQueue mSyncQueue;
+    @Mock
+    private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+    @Mock
+    private MainStage mMainStage;
+    @Mock
+    private SideStage mSideStage;
+    @Mock
+    private StageTaskUnfoldController mMainUnfoldController;
+    @Mock
+    private StageTaskUnfoldController mSideUnfoldController;
+    @Mock
+    private SplitLayout mSplitLayout;
+    @Mock
+    private DisplayImeController mDisplayImeController;
+    @Mock
+    private DisplayInsetsController mDisplayInsetsController;
+    @Mock
+    private Transitions mTransitions;
+    @Mock
+    private TransactionPool mTransactionPool;
+    @Mock
+    private SplitscreenEventLogger mLogger;
+
+    private final Rect mBounds1 = new Rect(10, 20, 30, 40);
+    private final Rect mBounds2 = new Rect(5, 10, 15, 20);
+
     private StageCoordinator mStageCoordinator;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
-                mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
-                mDisplayImeController, mDisplayInsetsController, null /* splitLayout */,
-                mTransitions, mTransactionPool, mLogger);
+        mStageCoordinator = createStageCoordinator(/* splitLayout */ null);
+
+        when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
+        when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
     }
 
     @Test
@@ -88,6 +117,38 @@
     }
 
     @Test
+    public void testDisplayAreaAppeared_initializesUnfoldControllers() {
+        mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class));
+
+        verify(mMainUnfoldController).init();
+        verify(mSideUnfoldController).init();
+    }
+
+    @Test
+    public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
+        mStageCoordinator = createStageCoordinator(mSplitLayout);
+        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+        clearInvocations(mMainUnfoldController, mSideUnfoldController);
+
+        mStageCoordinator.onLayoutChanged(mSplitLayout);
+
+        verify(mMainUnfoldController).onLayoutChanged(mBounds2);
+        verify(mSideUnfoldController).onLayoutChanged(mBounds1);
+    }
+
+    @Test
+    public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
+        mStageCoordinator = createStageCoordinator(mSplitLayout);
+        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
+        clearInvocations(mMainUnfoldController, mSideUnfoldController);
+
+        mStageCoordinator.onLayoutChanged(mSplitLayout);
+
+        verify(mMainUnfoldController).onLayoutChanged(mBounds1);
+        verify(mSideUnfoldController).onLayoutChanged(mBounds2);
+    }
+
+    @Test
     public void testRemoveFromSideStage() {
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
 
@@ -131,4 +192,27 @@
         verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true));
         verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
     }
+
+    private StageCoordinator createStageCoordinator(SplitLayout splitLayout) {
+        return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
+                mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
+                mDisplayImeController, mDisplayInsetsController, splitLayout,
+                mTransitions, mTransactionPool, mLogger, new UnfoldControllerProvider());
+    }
+
+    private class UnfoldControllerProvider implements
+            Provider<Optional<StageTaskUnfoldController>> {
+
+        private boolean isMain = true;
+
+        @Override
+        public Optional<StageTaskUnfoldController> get() {
+            if (isMain) {
+                isMain = false;
+                return Optional.of(mMainUnfoldController);
+            } else {
+                return Optional.of(mSideUnfoldController);
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 0916dd1..a5746a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -23,6 +23,7 @@
 
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -60,6 +61,7 @@
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
     @Mock private SyncTransactionQueue mSyncQueue;
+    @Mock private StageTaskUnfoldController mStageTaskUnfoldController;
     @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
     private SurfaceSession mSurfaceSession = new SurfaceSession();
     private SurfaceControl mSurfaceControl;
@@ -74,7 +76,8 @@
                 DEFAULT_DISPLAY,
                 mCallbacks,
                 mSyncQueue,
-                mSurfaceSession);
+                mSurfaceSession,
+                mStageTaskUnfoldController);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootTask.parentTaskId = INVALID_TASK_ID;
         mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -111,6 +114,28 @@
         verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
     }
 
+    @Test
+    public void testTaskAppeared_notifiesUnfoldListener() {
+        final ActivityManager.RunningTaskInfo task =
+                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+
+        mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
+
+        verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl));
+    }
+
+    @Test
+    public void testTaskVanished_notifiesUnfoldListener() {
+        final ActivityManager.RunningTaskInfo task =
+                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+        mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
+        clearInvocations(mStageTaskUnfoldController);
+
+        mStageTaskListener.onTaskVanished(task);
+
+        verify(mStageTaskUnfoldController).onTaskVanished(eq(task));
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testUnknownTaskVanished() {
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 36722d9..e5a8aa0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -57,12 +57,12 @@
 import android.view.IWindowSession;
 import android.view.InsetsState;
 import android.view.Surface;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.WindowMetrics;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskSnapshot;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -119,10 +119,9 @@
         }
 
         @Override
-        protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
-                boolean playRevealAnimation) {
+        protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) {
             // listen for removeView
-            if (mAddWindowForTask == taskId) {
+            if (mAddWindowForTask == removalInfo.taskId) {
                 mAddWindowForTask = 0;
             }
         }
@@ -172,9 +171,11 @@
                 eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
         assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
 
-        mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false);
+        StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+        removalInfo.taskId = windowInfo.taskInfo.taskId;
+        mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
         waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false));
+        verify(mStartingSurfaceDrawer).removeWindowSynced(any());
         assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
index a098a68..aad9528 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
@@ -83,8 +83,7 @@
                 createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
                 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
                 taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
-                100 /* delayRemovalTime */, new InsetsState(),
-                null /* clearWindow */, new TestShellExecutor());
+                new InsetsState(), null /* clearWindow */, new TestShellExecutor());
     }
 
     private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8822aea..3cb6454 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1613,7 +1613,9 @@
             AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT |
             AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER |
             AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT |
-            AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2;
+            AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2 |
+            AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT |
+            AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT;
 
     // Returns a boolean whether the attributes, format, bufferSizeInBytes, mode allow
     // power saving to be automatically enabled for an AudioTrack. Returns false if
@@ -1787,6 +1789,8 @@
                 | AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
         put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
                 | AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
+        put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+                | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
     }};
 
     /**
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index d82b00d..62c313a 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -211,10 +211,10 @@
         }
 
         @Override
-        public void writePacket(byte[] buffer, int count) {
+        public boolean writePacket(byte[] buffer, int count) {
             if (mCharacteristic == null) {
                 Log.w(TAG, "not ready to send packet yet");
-                return;
+                return false;
             }
 
             // Cache the previous buffer for writePacket so buffers aren't
@@ -223,12 +223,22 @@
                 mCachedBuffer = new byte[count];
             }
             System.arraycopy(buffer, 0, mCachedBuffer, 0, count);
-            mCharacteristic.setValue(mCachedBuffer);
+            if (!mCharacteristic.setValue(mCachedBuffer)) {
+                Log.w(TAG, "could not set characteristic value");
+                return false;
+            }
+
             if (DEBUG) {
                 logByteArray("Sent ", mCharacteristic.getValue(), 0,
                        mCharacteristic.getValue().length);
             }
-            mBluetoothGatt.writeCharacteristic(mCharacteristic);
+
+            if (!mBluetoothGatt.writeCharacteristic(mCharacteristic)) {
+                Log.w(TAG, "could not write characteristic to Bluetooth GATT");
+                return false;
+            }
+
+            return true;
         }
     }
 
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
index 92585ea..3ef9f4c 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
@@ -17,11 +17,14 @@
 package com.android.bluetoothmidiservice;
 
 import android.media.midi.MidiReceiver;
+import android.util.Log;
 
 import com.android.internal.midi.MidiConstants;
 import com.android.internal.midi.MidiFramer;
 
 import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Queue;
 
 /**
  * This class accumulates MIDI messages to form a MIDI packet.
@@ -52,6 +55,8 @@
 
     private final Object mLock = new Object();
 
+    private Queue<byte[]> mFailedToSendQueue = new ArrayDeque<byte[]>();
+
     // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
     private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
         @Override
@@ -59,6 +64,8 @@
                 throws IOException {
 
             synchronized (mLock) {
+                flushFailedToSendQueueLocked();
+
                 int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
                 byte status = msg[offset];
                 boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE);
@@ -227,11 +234,44 @@
         }
 
         if (mAccumulatedBytes > 0) {
-            mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes);
+            boolean wasSendSuccessful = mPacketReceiver.writePacket(mAccumulationBuffer,
+                    mAccumulatedBytes);
+
+            if (!wasSendSuccessful) {
+                byte[] failedBuffer = new byte[mAccumulatedBytes];
+                System.arraycopy(mAccumulationBuffer, 0, failedBuffer, 0, mAccumulatedBytes);
+                mFailedToSendQueue.add(failedBuffer);
+                Log.d(TAG, "Enqueued data into failed queue.");
+            }
+
             mAccumulatedBytes = 0;
             mPacketTimestamp = 0;
             mRunningStatus = 0;
-            mWritePending = true;
+            mWritePending = wasSendSuccessful;
+        }
+    }
+
+    private void flushFailedToSendQueueLocked() {
+        while (!mFailedToSendQueue.isEmpty()) {
+            while (mWritePending) {
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    // try again
+                    continue;
+                }
+            }
+            byte[] currentBuffer = mFailedToSendQueue.element();
+
+            boolean wasSendSuccessful = mPacketReceiver.writePacket(currentBuffer,
+                    currentBuffer.length);
+            mWritePending = wasSendSuccessful;
+            if (wasSendSuccessful) {
+                mFailedToSendQueue.remove();
+                Log.d(TAG, "Dequeued data from failed queue.");
+            } else {
+                return;
+            }
         }
     }
 }
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
index 12c8b9b..a2c880ac 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
@@ -30,8 +30,9 @@
         /** Called to write an accumulated packet.
          * @param buffer the packet buffer to write
          * @param count the number of bytes in the packet buffer to write
+         * @return whether the operation was successful
          */
-        public void writePacket(byte[] buffer, int count);
+        boolean writePacket(byte[] buffer, int count);
     }
 
     /**
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java
index 3285f59..8572efd 100644
--- a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiCodecTest.java
@@ -114,7 +114,7 @@
         // TODO Should this block?
         // Store the packets and then write them from a periodic task.
         @Override
-        public void writePacket(byte[] buffer, int count) {
+        public boolean writePacket(byte[] buffer, int count) {
             Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
             byte[] packet = new byte[count];
             System.arraycopy(buffer, 0, packet, 0, count);
@@ -124,6 +124,7 @@
                 assertEquals(null, e);
             }
             Log.d(TAG, "writePacket() returns");
+            return true;
         }
 
         void test(final byte[][] midi)
diff --git a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java
index d48b10a..1f432f6 100644
--- a/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java
+++ b/media/packages/BluetoothMidiService/tests/unit/src/com/android/bluetoothmidiservice/BluetoothMidiEncoderTest.java
@@ -42,11 +42,12 @@
     static class AccumulatingPacketReceiver implements PacketEncoder.PacketReceiver {
         ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
 
-        public void writePacket(byte[] buffer, int count) {
+        public boolean writePacket(byte[] buffer, int count) {
             byte[] actualRow = new byte[count];
             Log.d(TAG, "writePacket() passed " + MidiFramer.formatMidiData(buffer, 0, count));
             System.arraycopy(buffer, 0, actualRow, 0, count);
             mBuffers.add(actualRow);
+            return true;
         }
 
         byte[][] getBuffers() {
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 0b3f9bb..68b6091 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -48,13 +48,13 @@
 }
 
 genrule {
-  name: "generate_priv_manifest",
-  srcs: [
-    "shim_priv/AndroidManifest.xml",
-    ":CtsShimPrivUpgrade"
-  ],
-  out: ["AndroidManifest.xml"],
-  cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimPrivUpgrade) | cut -d' ' -f1`/ $(location shim_priv/AndroidManifest.xml) > $(out)",
+    name: "generate_priv_manifest",
+    srcs: [
+        "shim_priv/AndroidManifest.xml",
+        ":CtsShimPrivUpgrade",
+    ],
+    out: ["AndroidManifest.xml"],
+    cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimPrivUpgrade) | cut -d' ' -f1`/ $(location shim_priv/AndroidManifest.xml) > $(out)",
 }
 
 //##########################################################
@@ -141,6 +141,34 @@
 }
 
 //##########################################################
+// Variant: System app upgrade
+
+android_app {
+    name: "CtsShimUpgrade",
+
+    sdk_version: "current",
+    optimize: {
+        enabled: false,
+    },
+    dex_preopt: {
+        enabled: false,
+    },
+
+    manifest: "shim/AndroidManifestUpgrade.xml",
+    min_sdk_version: "24",
+}
+
+genrule {
+    name: "generate_shim_manifest",
+    srcs: [
+        "shim/AndroidManifest.xml",
+        ":CtsShimUpgrade",
+    ],
+    out: ["AndroidManifest.xml"],
+    cmd: "sed -e s/__HASH__/`sha512sum -b $(location :CtsShimUpgrade) | cut -d' ' -f1`/ $(location shim/AndroidManifest.xml) > $(out)",
+}
+
+//##########################################################
 // Variant: System app
 
 android_app {
@@ -154,7 +182,7 @@
         enabled: false,
     },
 
-    manifest: "shim/AndroidManifest.xml",
+    manifest: ":generate_shim_manifest",
     apex_available: [
         "//apex_available:platform",
         "com.android.apex.cts.shim.v1",
diff --git a/packages/CtsShim/build/shim/AndroidManifest.xml b/packages/CtsShim/build/shim/AndroidManifest.xml
index 1ffe56c..3b8276b6 100644
--- a/packages/CtsShim/build/shim/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim/AndroidManifest.xml
@@ -17,13 +17,15 @@
 <!-- Manifest for the system CTS shim -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.cts.ctsshim" >
+    package="com.android.cts.ctsshim"
+    android:sharedUserId="com.android.cts.ctsshim" >
 
-    <uses-sdk android:minSdkVersion="24"
+    <uses-sdk
+        android:minSdkVersion="24"
         android:targetSdkVersion="28" />
 
     <restrict-update
-        android:hash="__CAN_NOT_BE_UPDATED__" />
+        android:hash="__HASH__" />
 
     <application
         android:hasCode="false"
diff --git a/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
new file mode 100644
index 0000000..7f3644a
--- /dev/null
+++ b/packages/CtsShim/build/shim/AndroidManifestUpgrade.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Manifest for the system CTS shim -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.cts.ctsshim" >
+
+    <uses-sdk
+        android:minSdkVersion="24"
+        android:targetSdkVersion="28" />
+
+    <application
+        android:hasCode="false"
+        tools:ignore="AllowBackup,MissingApplicationIcon" />
+</manifest>
+
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 3ae9fa3..915db9c 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -331,7 +331,7 @@
     <string name="adb_warning_message" msgid="8145270656419669221">"USB bidezko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Hari gabeko arazketa baimendu nahi duzu?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"Hari gabeko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, gailuan aplikazioak jakinarazi gabe instalatzeko eta erregistroko datuak irakurtzeko."</string>
-    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Aurretik baimendutako ordenagailu guztiei USB bidezko arazketarako sarbidea baliogabetu nahi diezu?"</string>
+    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Aurretik baimendutako ordenagailu guztiei USB bidezko arazketarako sarbidea kendu nahi diezu?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"Baimendu garapenerako ezarpenak?"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Ezarpen hauek garapen-xedeetarako pentsatu dira soilik. Baliteke ezarpenen eraginez gailua matxuratzea edo funtzionamendu okerra izatea."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Egiaztatu USB bidezko aplik."</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 74585da..319e61a 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -332,7 +332,7 @@
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат бересизби?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"Мүчүлүштүктөрдү Wi-Fi аркылуу аныктоо – өндүрүү максатында гана түзүлгөн. Аны компьютериңиз менен түзмөгүңүздүн ортосунда маалыматты алмашуу, колдонмолорду түзмөгүңүзгө эскертүүсүз орнотуу жана маалыматтар таржымалын окуу үчүн колдонсоңуз болот."</string>
     <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB жөндөөлөрүнө уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
-    <string name="dev_settings_warning_title" msgid="8251234890169074553">"Жөндөөлөрдү өзгөртүү"</string>
+    <string name="dev_settings_warning_title" msgid="8251234890169074553">"Параметрлерди өзгөртүү"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Бул орнотуулар өндүрүүчүлөр үчүн гана берилген. Булар түзмөгүңүздүн колдонмолорун бузулушуна же туура эмес иштешине алып келиши мүмкүн."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Орнотулуучу колдонмону текшерүү"</string>
     <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"ADB/ADT аркылуу орнотулган колдонмолордун коопсуздугу текшерилет."</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index dd0b1f2..6faf614 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -182,7 +182,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"ଉପଯୋଗକର୍ତ୍ତା: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"କିଛି ପୂର୍ବ-ନିର୍ଦ୍ଧାରିତ ମାନ ସେଟ୍‌ ହୋଇଛି"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"କୌଣସି ଡିଫଲ୍ଟ ସେଟ୍‍ ହୋଇନାହିଁ"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"ଟେକ୍ସଟ-ରୁ-ସ୍ପିଚ୍ ସେଟିଂସ୍"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"ଟେକ୍ସଟ୍-ଟୁ-ସ୍ପିଚ୍ ସେଟିଂସ"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"ଟେକ୍ସଟ୍‍-ଟୁ-ସ୍ପିଚ୍‍ ଆଉଟ୍‍ପୁଟ୍‌"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"ସ୍ପିଚ୍‌ ରେଟ୍"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"ଲେଖା ପଢ଼ିବାର ବେଗ"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
index f9d3aaf..ba63a8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncher.java
@@ -102,7 +102,7 @@
     protected boolean isSameProfileGroup(Context context, int enforcementAdminUserId) {
         UserManager um = context.getSystemService(UserManager.class);
 
-        return um.isSameProfileGroup(enforcementAdminUserId, um.getUserHandle());
+        return um.isSameProfileGroup(enforcementAdminUserId, um.getProcessUserId());
     }
 
     private boolean isEnforcedByDeviceOwnerOnSystemUserMode(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java
index c2063c6..47556da 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ActionDisabledLearnMoreButtonLauncherTest.java
@@ -80,7 +80,7 @@
     @Before
     public void setUp() {
         when(mContext.getUserId()).thenReturn(CONTEXT_USER_ID);
-        when(mUserManager.getUserHandle()).thenReturn(CONTEXT_USER_ID);
+        when(mUserManager.getProcessUserId()).thenReturn(CONTEXT_USER_ID);
         when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDevicePolicyManager);
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b9eec6e..7f0c5d4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -464,6 +464,10 @@
     <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
     <uses-permission android:name="android.permission.SUGGEST_EXTERNAL_TIME" />
 
+    <!-- Permissions needed for testing locale manager service -->
+    <!-- todo(b/201957547): Add CTS test name when available-->
+    <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
+
     <!-- Permission required for CTS test - android.server.biometrics -->
     <uses-permission android:name="android.permission.USE_BIOMETRIC" />
 
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d7e4d72..9569cf9 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -143,6 +143,17 @@
     path: "tests/src",
 }
 
+java_library {
+    name: "SystemUI-tests-concurrency",
+    srcs: [
+        "src/com/android/systemui/util/concurrency/DelayableExecutor.java",
+        "src/com/android/systemui/util/time/SystemClock.java",
+        "tests/src/com/android/systemui/util/concurrency/FakeExecutor.java",
+        "tests/src/com/android/systemui/util/time/FakeSystemClock.java",
+    ],
+    jarjar_rules: ":jarjar-rules-shared",
+}
+
 android_library {
     name: "SystemUI-tests",
     manifest: "tests/AndroidManifest-base.xml",
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/keyguard.md
new file mode 100644
index 0000000..e3d48ae
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard.md
@@ -0,0 +1,12 @@
+# Keyguard (aka Lockscreen)
+
+Keyguard is responsible for:
+
+1. Handling authentication to allow the user to unlock the device, via biometrics or [KeyguardBouncer][1]
+2. Displaying informational content such as the time, notifications, and smartspace
+3. Always-on Display (AOD)
+
+Keyguard is the first screen available when turning on the device, as long as the user has not specified a security method of NONE.
+
+[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md
new file mode 100644
index 0000000..a724966
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard/bouncer.md
@@ -0,0 +1,18 @@
+# Bouncer
+
+[KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM.
+
+## Components
+
+The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts.
+
+1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility.
+  1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
+    1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
+       1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
+
+[1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer
+[2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController
+[3]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityContainerController
+[4]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityViewFlipperController
+[5]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityModel
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 0424382..a03d849 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -16,7 +16,6 @@
 
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
 
 import com.android.systemui.plugins.FragmentBase;
 import com.android.systemui.plugins.annotations.DependsOn;
@@ -56,7 +55,7 @@
     void setQsExpansion(float qsExpansionFraction, float headerTranslation);
     void setHeaderListening(boolean listening);
     void notifyCustomizeChanged();
-    void setContainer(ViewGroup container);
+    void setContainerController(QSContainerController controller);
     void setExpandClickListener(OnClickListener onClickListener);
 
     View getHeader();
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
new file mode 100644
index 0000000..8bf982d
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
@@ -0,0 +1,9 @@
+package com.android.systemui.plugins.qs
+
+interface QSContainerController {
+    fun setCustomizerAnimating(animating: Boolean)
+
+    fun setCustomizerShowing(showing: Boolean)
+
+    fun setDetailShowing(showing: Boolean)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 6c5c4ef..9829918 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -109,9 +109,8 @@
          * Callback to be notified when the fullscreen or immersive state changes.
          *
          * @param isFullscreen if any of the system bar is hidden by the focused window.
-         * @param isImmersive if the navigation bar can stay hidden when the display gets tapped.
          */
-        default void onFullscreenStateChanged(boolean isFullscreen, boolean isImmersive) {}
+        default void onFullscreenStateChanged(boolean isFullscreen) {}
 
         /**
          * Callback to be notified when the pulsing state changes
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index d9a5670..e02a1767 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -97,7 +97,7 @@
             android:singleLine="true"
             android:ellipsize="marquee"
             android:focusable="true" />
-        <FrameLayout android:id="@+id/keyboard_bouncer_container"
+        <FrameLayout android:id="@+id/keyguard_bouncer_container"
                      android:layout_height="0dp"
                      android:layout_width="match_parent"
                      android:layout_weight="1" />
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index ac93bf5..7a97f1ea 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -124,8 +124,8 @@
     <string name="screenrecord_permission_error" msgid="7856841237023137686">"No se han podido obtener los permisos"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
     <string name="usb_preference_title" msgid="1439924437558480718">"Opciones de transferencia de archivos por USB"</string>
-    <string name="use_mtp_button_title" msgid="5036082897886518086">"Activar como reproductor de medios (MTP)"</string>
-    <string name="use_ptp_button_title" msgid="7676427598943446826">"Activar como cámara (PTP)"</string>
+    <string name="use_mtp_button_title" msgid="5036082897886518086">"Montar como reproductor de medios (MTP)"</string>
+    <string name="use_ptp_button_title" msgid="7676427598943446826">"Montar como cámara (PTP)"</string>
     <string name="installer_cd_button_title" msgid="5499998592841984743">"Instalar Android File Transfer para Mac"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index e5206c0..fb3c9de 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -602,8 +602,8 @@
     <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Automatisk medieteksting"</string>
     <string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"Verktøytips for teksting"</string>
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Overlegg med teksting"</string>
-    <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"slå på"</string>
-    <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"slå av"</string>
+    <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"aktivér"</string>
+    <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"deaktiver"</string>
     <string name="accessibility_output_chooser" msgid="7807898688967194183">"Bytt enhet for lydutgang"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"Appen er festet"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"Gjør at den vises til du løsner den. Trykk og hold inne Tilbake og Oversikt for å løsne den."</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8109e23..6f8e2f1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1599,7 +1599,7 @@
 
     <!-- Internet panel related dimensions -->
     <dimen name="internet_dialog_list_margin">12dp</dimen>
-    <dimen name="internet_dialog_list_max_height">646dp</dimen>
+    <dimen name="internet_dialog_list_max_height">662dp</dimen>
 
     <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
     <dimen name="large_dialog_width">@dimen/match_parent</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 0386217..08f72cb 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -51,6 +51,8 @@
 
     <bool name="flag_ongoing_call_status_bar_chip">true</bool>
 
+    <bool name="flag_ongoing_call_in_immersive">false</bool>
+
     <bool name="flag_smartspace">false</bool>
 
     <bool name="flag_smartspace_deduping">true</bool>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 3a23094..4880b12 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -43,6 +43,7 @@
         "src/**/*.kt",
         "src/**/*.aidl",
         ":wm_shell-aidls",
+        ":wm_shell_util-sources",
     ],
 
     static_libs: [
@@ -50,5 +51,5 @@
         "androidx.dynamicanimation_dynamicanimation",
     ],
     java_version: "1.8",
-    min_sdk_version: "26",
+    min_sdk_version: "current",
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 7aee721..dcc4ea1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -40,7 +40,7 @@
 import android.window.RemoteTransition;
 import android.window.TransitionInfo;
 
-import java.util.ArrayList;
+import com.android.wm.shell.util.CounterRotator;
 
 /**
  * @see RemoteAnimationAdapter
@@ -109,52 +109,6 @@
         };
     }
 
-    private static class CounterRotator {
-        SurfaceControl mSurface = null;
-        ArrayList<SurfaceControl> mRotateChildren = null;
-
-        void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
-                float displayW, float displayH) {
-            if (rotateDelta == 0) return;
-            mRotateChildren = new ArrayList<>();
-            // We want to counter-rotate, so subtract from 4
-            rotateDelta = 4 - (rotateDelta + 4) % 4;
-            mSurface = new SurfaceControl.Builder()
-                    .setName("Transition Unrotate")
-                    .setContainerLayer()
-                    .setParent(parent)
-                    .build();
-            // column-major
-            if (rotateDelta == 1) {
-                t.setMatrix(mSurface, 0, 1, -1, 0);
-                t.setPosition(mSurface, displayW, 0);
-            } else if (rotateDelta == 2) {
-                t.setMatrix(mSurface, -1, 0, 0, -1);
-                t.setPosition(mSurface, displayW, displayH);
-            } else if (rotateDelta == 3) {
-                t.setMatrix(mSurface, 0, -1, 1, 0);
-                t.setPosition(mSurface, 0, displayH);
-            }
-            t.show(mSurface);
-        }
-
-        void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
-            if (mSurface == null) return;
-            t.reparent(child, mSurface);
-            mRotateChildren.add(child);
-        }
-
-        void cleanUp(SurfaceControl rootLeash) {
-            if (mSurface == null) return;
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
-                t.reparent(mRotateChildren.get(i), rootLeash);
-            }
-            t.remove(mSurface);
-            t.apply();
-        }
-    }
-
     private static IRemoteTransition.Stub wrapRemoteTransition(
             final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
         return new IRemoteTransition.Stub() {
@@ -204,14 +158,14 @@
                 if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
                     counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
                             rotateDelta, displayW, displayH);
-                    if (counterLauncher.mSurface != null) {
-                        t.setLayer(counterLauncher.mSurface, launcherLayer);
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
                     }
                 }
 
                 if (isReturnToHome) {
-                    if (counterLauncher.mSurface != null) {
-                        t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
                     }
                     // Need to "boost" the closing things since that's what launcher expects.
                     for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -237,8 +191,8 @@
                     if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
                         counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
                                 rotateDelta, displayW, displayH);
-                        if (counterWallpaper.mSurface != null) {
-                            t.setLayer(counterWallpaper.mSurface, -1);
+                        if (counterWallpaper.getSurface() != null) {
+                            t.setLayer(counterWallpaper.getSurface(), -1);
                             counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index efcf40a..a383cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -20,12 +20,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.icu.text.NumberFormat;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -67,20 +71,20 @@
             BroadcastDispatcher broadcastDispatcher,
             BatteryController batteryController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController bypassController) {
+            KeyguardBypassController bypassController,
+            @Main Resources resources
+    ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
-        mIsDozing = mStatusBarStateController.isDozing();
-        mDozeAmount = mStatusBarStateController.getDozeAmount();
         mBroadcastDispatcher = broadcastDispatcher;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mBypassController = bypassController;
         mBatteryController = batteryController;
 
         mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
-        mBurmeseLineSpacing = getContext().getResources().getFloat(
+        mBurmeseLineSpacing = resources.getFloat(
                 R.dimen.keyguard_clock_line_spacing_scale_burmese);
-        mDefaultLineSpacing = getContext().getResources().getFloat(
+        mDefaultLineSpacing = resources.getFloat(
                 R.dimen.keyguard_clock_line_spacing_scale);
     }
 
@@ -106,7 +110,7 @@
         }
     };
 
-    private final StatusBarStateController.StateListener mStatusBarStatePersistentListener =
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
@@ -144,11 +148,11 @@
         mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
                 new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
         mDozeAmount = mStatusBarStateController.getDozeAmount();
+        mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
         mBatteryController.addCallback(mBatteryCallback);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
 
-        mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
-        mStatusBarStateController.addCallback(mStatusBarStatePersistentListener);
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
 
         refreshTime();
         initColors();
@@ -160,7 +164,7 @@
         mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
         mBatteryController.removeCallback(mBatteryCallback);
-        mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
     /** Animate the clock appearance */
@@ -189,6 +193,14 @@
         mView.refreshFormat();
     }
 
+    /**
+     * Return locallly stored dozing state.
+     */
+    @VisibleForTesting
+    public boolean isDozing() {
+        return mIsDozing;
+    }
+
     private void updateLocale() {
         Locale currLocale = Locale.getDefault();
         if (!Objects.equals(currLocale, mLocale)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 260b393..4111020 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 
 import android.app.WallpaperManager;
+import android.content.res.Resources;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -32,6 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -67,6 +69,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final BatteryController mBatteryController;
     private final LockscreenSmartspaceController mSmartspaceController;
+    private final Resources mResources;
 
     /**
      * Clock for both small and large sizes
@@ -118,7 +121,8 @@
             KeyguardBypassController bypassController,
             LockscreenSmartspaceController smartspaceController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            SmartspaceTransitionController smartspaceTransitionController) {
+            SmartspaceTransitionController smartspaceTransitionController,
+            @Main Resources resources) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mColorExtractor = colorExtractor;
@@ -130,6 +134,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mBypassController = bypassController;
         mSmartspaceController = smartspaceController;
+        mResources = resources;
 
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mSmartspaceTransitionController = smartspaceTransitionController;
@@ -159,7 +164,8 @@
                         mBroadcastDispatcher,
                         mBatteryController,
                         mKeyguardUpdateMonitor,
-                        mBypassController);
+                        mBypassController,
+                        mResources);
         mClockViewController.init();
 
         mLargeClockViewController =
@@ -169,7 +175,8 @@
                         mBroadcastDispatcher,
                         mBatteryController,
                         mKeyguardUpdateMonitor,
-                        mBypassController);
+                        mBypassController,
+                        mResources);
         mLargeClockViewController.init();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index a5b18ca..abd89b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -21,6 +21,8 @@
 
 import static java.lang.Integer.max;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -35,7 +37,6 @@
 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.WindowManager;
@@ -43,6 +44,7 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -111,7 +113,7 @@
 
     private boolean mIsSecurityViewLeftAligned = true;
     private boolean mOneHandedMode = false;
-    private ViewPropertyAnimator mRunningOneHandedAnimator;
+    @Nullable private ValueAnimator mRunningOneHandedAnimator;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
             new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -347,9 +349,9 @@
             Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
             Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
 
-            ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 1.0f);
-            anim.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
-            anim.setInterpolator(Interpolators.LINEAR);
+            mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+            mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
+            mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
 
             int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
             int totalTranslation = (int) getResources().getDimension(
@@ -361,7 +363,15 @@
                 mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
             }
 
-            anim.addUpdateListener(animation -> {
+            float initialAlpha = mSecurityViewFlipper.getAlpha();
+
+            mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRunningOneHandedAnimator = null;
+                }
+            });
+            mRunningOneHandedAnimator.addUpdateListener(animation -> {
                 float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
                 boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
 
@@ -378,13 +388,16 @@
                 if (isFadingOut) {
                     // The bouncer fades out over the first X%.
                     float fadeOutFraction = MathUtils.constrainedMap(
-                            /* rangeMin= */0.0f,
-                            /* rangeMax= */1.0f,
+                            /* rangeMin= */1.0f,
+                            /* rangeMax= */0.0f,
                             /* valueMin= */0.0f,
                             /* valueMax= */switchPoint,
                             animation.getAnimatedFraction());
                     float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-                    mSecurityViewFlipper.setAlpha(1f - opacity);
+
+                    // When fading out, the alpha needs to start from the initial opacity of the
+                    // view flipper, otherwise we get a weird bit of jank as it ramps back to 100%.
+                    mSecurityViewFlipper.setAlpha(opacity * initialAlpha);
 
                     // Animate away from the source.
                     mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
@@ -409,7 +422,7 @@
                 }
             });
 
-            anim.start();
+            mRunningOneHandedAnimator.start();
         } else {
             mSecurityViewFlipper.setTranslationX(targetTranslation);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index ecc8c00..db729da 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -18,7 +18,6 @@
 
 import android.os.Bundle;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -186,14 +185,12 @@
     /**
      * Registers the StatusBar to which this Keyguard View is mounted.
      * @param statusBar
-     * @param container
      * @param notificationPanelViewController
      * @param biometricUnlockController
      * @param notificationContainer
      * @param bypassController
      */
     void registerStatusBar(StatusBar statusBar,
-            ViewGroup container,
             NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 60b0637..da69f45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -824,11 +824,29 @@
         return new AuthDialog.LayoutParams(width, totalHeight);
     }
 
+    /**
+     * Simple heuristic which should return true displays that are larger than a normal phone.
+     * For example, tablet displays, or the unfolded display for foldables.
+     */
+    private boolean isLargeDisplay(int width, int height) {
+        return width > 1200 && height > 1200;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int height = MeasureSpec.getSize(heightMeasureSpec);
-        final int newWidth = Math.min(width, height);
+
+        Log.d(TAG, "Width: " + width + ", height: " + height);
+
+        final int newWidth;
+        if (isLargeDisplay(width, height)) {
+            // TODO: Unless we can come up with a one-size-fits-all equation, we may want to
+            //  consider moving this to an overlay.
+            newWidth = 2 * Math.min(width, height) / 3;
+        } else {
+            newWidth = Math.min(width, height);
+        }
 
         // Use "newWidth" instead, so the landscape dialog width is the same as the portrait
         // width.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index fa50f89..f1e42e0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -117,24 +117,6 @@
         mUseFullScreen = fullScreen;
     }
 
-    public ValueAnimator getTranslationAnimator(float relativeTranslationY) {
-        final ValueAnimator animator = ValueAnimator.ofFloat(
-                mPanelView.getY(), mPanelView.getY() - relativeTranslationY);
-        animator.addUpdateListener(animation -> {
-            final float translation = (float) animation.getAnimatedValue();
-            mPanelView.setTranslationY(translation);
-        });
-        return animator;
-    }
-
-    public ValueAnimator getAlphaAnimator(float alpha) {
-        final ValueAnimator animator = ValueAnimator.ofFloat(mPanelView.getAlpha(), alpha);
-        animator.addUpdateListener(animation -> {
-            mPanelView.setAlpha((float) animation.getAnimatedValue());
-        });
-        return animator;
-    }
-
     public void updateForContentDimensions(int contentWidth, int contentHeight,
             int animateDurationMs) {
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index eb6b193..0932a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -18,7 +18,6 @@
 
 import android.animation.ValueAnimator
 import android.content.Context
-import android.content.res.Configuration
 import android.graphics.PointF
 import android.hardware.biometrics.BiometricSourceType
 import android.util.DisplayMetrics
@@ -125,6 +124,7 @@
             return
         }
 
+        updateSensorLocation()
         if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
             fingerprintSensorLocation != null) {
             mView.setSensorLocation(fingerprintSensorLocation!!)
@@ -266,9 +266,6 @@
 
     private val configurationChangedListener =
         object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                updateSensorLocation()
-            }
             override fun onUiModeChanged() {
                 updateRippleColor()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index d3d6e03..6f30ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.MainThread
 import android.app.Dialog
+import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
@@ -88,7 +89,7 @@
         bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             if (cvh.usePanel()) {
-                showDetail(cvh, control.getAppIntent().getIntent())
+                showDetail(cvh, control.getAppIntent())
             } else {
                 cvh.action(CommandAction(templateId))
             }
@@ -116,7 +117,7 @@
             // Long press snould only be called when there is valid control state, otherwise ignore
             cvh.cws.control?.let {
                 cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
-                showDetail(cvh, it.getAppIntent().getIntent())
+                showDetail(cvh, it.getAppIntent())
             }
         }, false /* blockable */))
     }
@@ -167,10 +168,10 @@
         bgExecutor.execute { vibrator.vibrate(effect) }
     }
 
-    private fun showDetail(cvh: ControlViewHolder, intent: Intent) {
+    private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) {
         bgExecutor.execute {
             val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities(
-                intent,
+                pendingIntent.getIntent(),
                 PackageManager.MATCH_DEFAULT_ONLY
             )
 
@@ -178,7 +179,7 @@
                 // make sure the intent is valid before attempting to open the dialog
                 if (activities.isNotEmpty() && taskViewFactory.isPresent) {
                     taskViewFactory.get().create(context, uiExecutor, {
-                        dialog = DetailDialog(activityContext, it, intent, cvh).also {
+                        dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also {
                             it.setOnDismissListener { _ -> dialog = null }
                             it.show()
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 8a47a36..4758ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -43,7 +43,7 @@
 class DetailDialog(
     val activityContext: Context?,
     val taskView: TaskView,
-    val intent: Intent,
+    val pendingIntent: PendingIntent,
     val cvh: ControlViewHolder
 ) : Dialog(
     activityContext ?: cvh.context,
@@ -59,6 +59,14 @@
 
     var detailTaskId = INVALID_TASK_ID
 
+    private val fillInIntent = Intent().apply {
+        putExtra(EXTRA_USE_PANEL, true)
+
+        // Apply flags to make behaviour match documentLaunchMode=always.
+        addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+        addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+    }
+
     fun removeDetailTask() {
         if (detailTaskId == INVALID_TASK_ID) return
         ActivityTaskManager.getInstance().removeTask(detailTaskId)
@@ -67,13 +75,6 @@
 
     val stateCallback = object : TaskView.Listener {
         override fun onInitialized() {
-            val launchIntent = Intent(intent)
-            launchIntent.putExtra(EXTRA_USE_PANEL, true)
-
-            // Apply flags to make behaviour match documentLaunchMode=always.
-            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
-            launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
-
             val options = activityContext?.let {
                 ActivityOptions.makeCustomAnimation(
                     it,
@@ -82,9 +83,8 @@
                 )
             } ?: ActivityOptions.makeBasic()
             taskView.startActivity(
-                PendingIntent.getActivity(context, 0, launchIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE),
-                null /* fillInIntent */,
+                pendingIntent,
+                fillInIntent,
                 options,
                 getTaskViewBounds()
             )
@@ -97,6 +97,9 @@
 
         override fun onTaskCreated(taskId: Int, name: ComponentName?) {
             detailTaskId = taskId
+            requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
+                setAlpha(1f)
+            }
         }
 
         override fun onReleased() {
@@ -121,6 +124,7 @@
 
         requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
             addView(taskView)
+            setAlpha(0f)
         }
 
         requireViewById<ImageView>(R.id.control_detail_close).apply {
@@ -134,7 +138,7 @@
                 removeDetailTask()
                 dismiss()
                 context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
-                v.context.startActivity(intent)
+                pendingIntent.send()
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index 432d6c2..77bd777 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -123,6 +123,11 @@
         return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
     }
 
+    public boolean isOngoingCallInImmersiveEnabled() {
+        return isOngoingCallStatusBarChipEnabled()
+                && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
+    }
+
     public boolean isSmartspaceEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_smartspace);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1561eb6..5265718 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -77,7 +77,6 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 import android.view.animation.Animation;
@@ -1475,7 +1474,9 @@
     public void doKeyguardTimeout(Bundle options) {
         mHandler.removeMessages(KEYGUARD_TIMEOUT);
         Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT, options);
-        mHandler.sendMessage(msg);
+        // Treat these messages with priority - A call to timeout means the device should lock
+        // as soon as possible and not wait for other messages on the thread to process first.
+        mHandler.sendMessageAtFrontOfQueue(msg);
     }
 
     /**
@@ -1664,12 +1665,15 @@
      * @see #handleShow
      */
     private void showLocked(Bundle options) {
-        Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock");
+        Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock");
         if (DEBUG) Log.d(TAG, "showLocked");
         // ensure we stay awake until we are finished displaying the keyguard
         mShowKeyguardWakeLock.acquire();
         Message msg = mHandler.obtainMessage(SHOW, options);
-        mHandler.sendMessage(msg);
+        // Treat these messages with priority - This call can originate from #doKeyguardTimeout,
+        // meaning the device should lock as soon as possible and not wait for other messages on
+        // the thread to process first.
+        mHandler.sendMessageAtFrontOfQueue(msg);
         Trace.endSection();
     }
 
@@ -1855,6 +1859,7 @@
                 case KEYGUARD_TIMEOUT:
                     synchronized (KeyguardViewMediator.this) {
                         doKeyguardLocked((Bundle) msg.obj);
+                        notifyDefaultDisplayCallbacks(mShowing);
                     }
                     break;
                 case DISMISS:
@@ -2623,7 +2628,6 @@
      * Registers the StatusBar to which the Keyguard View is mounted.
      *
      * @param statusBar
-     * @param container
      * @param panelView
      * @param biometricUnlockController
      * @param notificationContainer
@@ -2631,10 +2635,10 @@
      * @return the View Controller for the Keyguard View this class is mediating.
      */
     public KeyguardViewController registerStatusBar(StatusBar statusBar,
-            ViewGroup container, NotificationPanelViewController panelView,
+            NotificationPanelViewController panelView,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer, KeyguardBypassController bypassController) {
-        mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, container, panelView,
+        mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, panelView,
                 biometricUnlockController, notificationContainer, bypassController);
         return mKeyguardViewControllerLazy.get();
     }
@@ -2840,7 +2844,7 @@
             for (int i = size - 1; i >= 0; i--) {
                 IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
                 try {
-                    callback.onShowingStateChanged(showing);
+                    callback.onShowingStateChanged(showing, KeyguardUpdateMonitor.getCurrentUser());
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to call onShowingStateChanged", e);
                     if (e instanceof DeadObjectException) {
@@ -2889,7 +2893,7 @@
             mKeyguardStateCallbacks.add(callback);
             try {
                 callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
-                callback.onShowingStateChanged(mShowing);
+                callback.onShowingStateChanged(mShowing, KeyguardUpdateMonitor.getCurrentUser());
                 callback.onInputRestrictedStateChanged(mInputRestricted);
                 callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
                         KeyguardUpdateMonitor.getCurrentUser()));
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c743fe1..0e70945 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -833,11 +833,12 @@
     )
 
     private val comparator =
-            compareByDescending<MediaSortKey> { it.data.isPlaying }
+            compareByDescending<MediaSortKey> { it.data.isPlaying == true && it.data.isLocalSession }
+                    .thenByDescending { it.data.isPlaying }
                     .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
-                    .thenByDescending { it.data.isLocalSession }
                     .thenByDescending { !it.data.resumption }
                     .thenByDescending { it.updateTime }
+                    .thenByDescending { !it.data.isLocalSession }
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 06a1eea..3631d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -768,7 +768,7 @@
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
             val updated = removed.copy(token = null, actions = listOf(resumeAction),
                     actionsToShowInCompact = listOf(0), active = false, resumption = true,
-                    isClearable = true)
+                    isPlaying = false, isClearable = true)
             val pkg = removed.packageName
             val migrate = mediaEntries.put(pkg, updated) == null
             // Notify listeners of "new" controls when migrating or removed and update when not
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index c6da342..fa37c86 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -39,7 +39,6 @@
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
 import android.view.InputDevice;
@@ -105,9 +104,6 @@
     static final boolean DEBUG_MISSING_GESTURE = true;
     static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
 
-    private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
-            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
-
     private ISystemGestureExclusionListener mGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
                 @Override
@@ -559,16 +555,6 @@
     private void onInputEvent(InputEvent ev) {
         if (!(ev instanceof MotionEvent)) return;
         MotionEvent event = (MotionEvent) ev;
-        if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
-            final Display display = mContext.getDisplay();
-            int rotation = display.getRotation();
-            if (rotation != Surface.ROTATION_0) {
-                Point sz = new Point();
-                display.getRealSize(sz);
-                event = MotionEvent.obtain(event);
-                event.transform(MotionEvent.createRotateMatrix(rotation, sz.x, sz.y));
-            }
-        }
         onMotionEvent(event);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 8e43661..3fc4f50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -26,7 +26,6 @@
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.WindowInsets;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -58,7 +57,6 @@
     private int mSideMargins;
     private boolean mQsDisabled;
     private int mContentPadding = -1;
-    private int mNavBarInset = 0;
     private boolean mClippingEnabled;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -95,24 +93,13 @@
     }
 
     @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
-        mQSPanelContainer.setPaddingRelative(
-                mQSPanelContainer.getPaddingStart(),
-                mQSPanelContainer.getPaddingTop(),
-                mQSPanelContainer.getPaddingEnd(),
-                mNavBarInset
-        );
-        return super.onApplyWindowInsets(insets);
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
         // bottom and footer are inside the screen.
         MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
 
-        int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
+        int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);
+        int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
                 - getPaddingBottom();
         int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
                 + layoutParams.rightMargin;
@@ -122,11 +109,11 @@
                 MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
         int width = mQSPanelContainer.getMeasuredWidth() + padding;
         super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
+                MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
         // QSCustomizer will always be the height of the screen, but do this after
         // other measuring to avoid changing the height of the QS.
         mQSCustomizer.measure(widthMeasureSpec,
-                MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
+                MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 929927e..58a942a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -46,8 +46,8 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 
 public class QSDetail extends LinearLayout {
 
@@ -86,7 +86,7 @@
     private boolean mSwitchState;
     private QSFooter mFooter;
 
-    private NotificationsQuickSettingsContainer mContainer;
+    private QSContainerController mQsContainerController;
 
     public QSDetail(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -120,8 +120,8 @@
         mClipper = new QSDetailClipper(this);
     }
 
-    public void setContainer(NotificationsQuickSettingsContainer container) {
-        mContainer = container;
+    public void setContainerController(QSContainerController controller) {
+        mQsContainerController = controller;
     }
 
     /** */
@@ -262,8 +262,8 @@
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         animateDetailVisibleDiff(x, y, visibleDiff, listener);
-        if (mContainer != null) {
-            mContainer.setDetailShowing(showingDetail);
+        if (mQsContainerController != null) {
+            mQsContainerController.setDetailShowing(showingDetail);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index a1aff57..ad3135e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -44,6 +44,7 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -51,7 +52,6 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
 import com.android.systemui.util.InjectionInflationController;
@@ -337,11 +337,9 @@
     }
 
     @Override
-    public void setContainer(ViewGroup container) {
-        if (container instanceof NotificationsQuickSettingsContainer) {
-            mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container);
-            mQSDetail.setContainer((NotificationsQuickSettingsContainer) container);
-        }
+    public void setContainerController(QSContainerController controller) {
+        mQSCustomizerController.setContainerController(controller);
+        mQSDetail.setContainerController(controller);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index e6ab436..c692252 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -33,9 +33,9 @@
 
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 
 /**
  * Allows full-screen customization of QS, through show() and hide().
@@ -54,7 +54,7 @@
     private boolean isShown;
     private final RecyclerView mRecyclerView;
     private boolean mCustomizing;
-    private NotificationsQuickSettingsContainer mNotifQsContainer;
+    private QSContainerController mQsContainerController;
     private QS mQs;
     private int mX;
     private int mY;
@@ -103,8 +103,8 @@
         lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
     }
 
-    public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
-        mNotifQsContainer = notificationsQsContainer;
+    public void setContainerController(QSContainerController controller) {
+        mQsContainerController = controller;
     }
 
     public void setQs(QS qs) {
@@ -124,8 +124,8 @@
             mOpening = true;
             setVisibility(View.VISIBLE);
             mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
-            mNotifQsContainer.setCustomizerAnimating(true);
-            mNotifQsContainer.setCustomizerShowing(true);
+            mQsContainerController.setCustomizerAnimating(true);
+            mQsContainerController.setCustomizerShowing(true);
         }
     }
 
@@ -138,8 +138,8 @@
             mClipper.showBackground();
             isShown = true;
             setCustomizing(true);
-            mNotifQsContainer.setCustomizerAnimating(false);
-            mNotifQsContainer.setCustomizerShowing(true);
+            mQsContainerController.setCustomizerAnimating(false);
+            mQsContainerController.setCustomizerShowing(true);
         }
     }
 
@@ -156,8 +156,8 @@
             } else {
                 setVisibility(View.GONE);
             }
-            mNotifQsContainer.setCustomizerAnimating(animate);
-            mNotifQsContainer.setCustomizerShowing(false);
+            mQsContainerController.setCustomizerAnimating(animate);
+            mQsContainerController.setCustomizerShowing(false);
         }
     }
 
@@ -195,7 +195,7 @@
                 setCustomizing(true);
             }
             mOpening = false;
-            mNotifQsContainer.setCustomizerAnimating(false);
+            mQsContainerController.setCustomizerAnimating(false);
             mRecyclerView.setAdapter(mTileAdapter);
         }
 
@@ -203,7 +203,7 @@
         public void onAnimationCancel(Animator animation) {
             mOpening = false;
             mQs.notifyCustomizeChanged();
-            mNotifQsContainer.setCustomizerAnimating(false);
+            mQsContainerController.setCustomizerAnimating(false);
         }
     }
 
@@ -213,7 +213,7 @@
             if (!isShown) {
                 setVisibility(View.GONE);
             }
-            mNotifQsContainer.setCustomizerAnimating(false);
+            mQsContainerController.setCustomizerAnimating(false);
         }
 
         @Override
@@ -221,7 +221,7 @@
             if (!isShown) {
                 setVisibility(View.GONE);
             }
-            mNotifQsContainer.setCustomizerAnimating(false);
+            mQsContainerController.setCustomizerAnimating(false);
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 49d18e6..618a429 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -35,13 +35,13 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QSContainerController;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSEditEvent;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -233,8 +233,8 @@
     }
 
     /** */
-    public void setContainer(NotificationsQuickSettingsContainer container) {
-        mView.setContainer(container);
+    public void setContainerController(QSContainerController controller) {
+        mView.setContainerController(controller);
     }
 
     public boolean isShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 7d26857..a0118ab 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -422,11 +422,6 @@
         } else {
             mScreenshotView.animateDismissal();
         }
-
-        if (mLastScrollCaptureResponse != null) {
-            mLastScrollCaptureResponse.close();
-            mLastScrollCaptureResponse = null;
-        }
     }
 
     boolean isPendingSharedTransition() {
@@ -456,6 +451,9 @@
         mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
+                if (DEBUG_INPUT) {
+                    Log.d(TAG, "onUserInteraction");
+                }
                 resetTimeout();
             }
 
@@ -670,52 +668,7 @@
                 mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
                         mScreenshotTakenInPortrait);
                 // delay starting scroll capture to make sure the scrim is up before the app moves
-                mScreenshotView.post(() -> {
-                    // Clear the reference to prevent close() in dismissScreenshot
-                    mLastScrollCaptureResponse = null;
-                    final ListenableFuture<ScrollCaptureController.LongScreenshot> future =
-                            mScrollCaptureController.run(response);
-                    future.addListener(() -> {
-                        ScrollCaptureController.LongScreenshot longScreenshot;
-
-                        try {
-                            longScreenshot = future.get();
-                        } catch (CancellationException
-                                | InterruptedException
-                                | ExecutionException e) {
-                            Log.e(TAG, "Exception", e);
-                            mScreenshotView.restoreNonScrollingUi();
-                            return;
-                        }
-
-                        if (longScreenshot.getHeight() == 0) {
-                            mScreenshotView.restoreNonScrollingUi();
-                            return;
-                        }
-
-                        mLongScreenshotHolder.setLongScreenshot(longScreenshot);
-                        mLongScreenshotHolder.setTransitionDestinationCallback(
-                                (transitionDestination, onTransitionEnd) ->
-                                        mScreenshotView.startLongScreenshotTransition(
-                                                transitionDestination, onTransitionEnd,
-                                                longScreenshot));
-
-                        final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
-                        intent.setFlags(
-                                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
-                        mContext.startActivity(intent,
-                                ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
-                        RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
-                                SCREENSHOT_REMOTE_RUNNER, 0, 0);
-                        try {
-                            WindowManagerGlobal.getWindowManagerService()
-                                    .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
-                        } catch (Exception e) {
-                            Log.e(TAG, "Error overriding screenshot app transition", e);
-                        }
-                    }, mMainExecutor);
-                });
+                mScreenshotView.post(() -> runBatchScrollCapture(response));
             });
         } catch (CancellationException e) {
             // Ignore
@@ -723,6 +676,57 @@
             Log.e(TAG, "requestScrollCapture failed", e);
         }
     }
+    ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
+
+    private void runBatchScrollCapture(ScrollCaptureResponse response) {
+        // Clear the reference to prevent close() in dismissScreenshot
+        mLastScrollCaptureResponse = null;
+
+        if (mLongScreenshotFuture != null) {
+            mLongScreenshotFuture.cancel(true);
+        }
+        mLongScreenshotFuture = mScrollCaptureController.run(response);
+        mLongScreenshotFuture.addListener(() -> {
+            ScrollCaptureController.LongScreenshot longScreenshot;
+            try {
+                longScreenshot = mLongScreenshotFuture.get();
+            } catch (CancellationException e) {
+                Log.e(TAG, "Long screenshot cancelled");
+                return;
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "Exception", e);
+                mScreenshotView.restoreNonScrollingUi();
+                return;
+            }
+
+            if (longScreenshot.getHeight() == 0) {
+                mScreenshotView.restoreNonScrollingUi();
+                return;
+            }
+
+            mLongScreenshotHolder.setLongScreenshot(longScreenshot);
+            mLongScreenshotHolder.setTransitionDestinationCallback(
+                    (transitionDestination, onTransitionEnd) ->
+                            mScreenshotView.startLongScreenshotTransition(
+                                    transitionDestination, onTransitionEnd,
+                                    longScreenshot));
+
+            final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+            mContext.startActivity(intent,
+                    ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
+            RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
+                    SCREENSHOT_REMOTE_RUNNER, 0, 0);
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
+            } catch (Exception e) {
+                Log.e(TAG, "Error overriding screenshot app transition", e);
+            }
+        }, mMainExecutor);
+    }
 
     private void withWindowAttached(Runnable action) {
         View decorView = mWindow.getDecorView();
@@ -824,16 +828,27 @@
 
     /** Reset screenshot view and then call onCompleteRunnable */
     private void finishDismiss() {
-        if (DEBUG_UI) {
+        if (DEBUG_DISMISS) {
             Log.d(TAG, "finishDismiss");
         }
-        cancelTimeout();
-        removeWindow();
-        mScreenshotView.reset();
+        if (mLastScrollCaptureRequest != null) {
+            mLastScrollCaptureRequest.cancel(true);
+            mLastScrollCaptureRequest = null;
+        }
+        if (mLastScrollCaptureResponse != null) {
+            mLastScrollCaptureResponse.close();
+            mLastScrollCaptureResponse = null;
+        }
+        if (mLongScreenshotFuture != null) {
+            mLongScreenshotFuture.cancel(true);
+        }
         if (mCurrentRequestCallback != null) {
             mCurrentRequestCallback.onFinish();
             mCurrentRequestCallback = null;
         }
+        mScreenshotView.reset();
+        removeWindow();
+        cancelTimeout();
     }
 
     /**
@@ -861,6 +876,9 @@
     }
 
     private void cancelTimeout() {
+        if (DEBUG_DISMISS) {
+            Log.d(TAG, "cancel timeout");
+        }
         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
     }
 
@@ -876,7 +894,7 @@
         mScreenshotHandler.sendMessageDelayed(
                 mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
                 timeoutMs);
-        if (DEBUG_UI) {
+        if (DEBUG_DISMISS) {
             Log.d(TAG, "dismiss timeout: " + timeoutMs + " ms");
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e812397..3314c75 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -1117,9 +1117,11 @@
                 mPreviousX = mStartX;
                 return true;
             } else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                if (isPastDismissThreshold()
-                        && (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
-                    if (DEBUG_INPUT) {
+                if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+                    return true;
+                }
+                if (isPastDismissThreshold()) {
+                    if (DEBUG_DISMISS) {
                         Log.d(TAG, "dismiss triggered via swipe gesture");
                     }
                     mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED);
@@ -1129,9 +1131,7 @@
                     if (DEBUG_DISMISS) {
                         Log.d(TAG, "swipe gesture abandoned");
                     }
-                    if ((mDismissAnimation == null || !mDismissAnimation.isRunning())) {
-                        createSwipeReturnAnimation().start();
-                    }
+                    createSwipeReturnAnimation().start();
                 }
                 return true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index ef7355a..83b60fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -35,6 +35,7 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 
@@ -72,6 +73,7 @@
     private ListenableFuture<CaptureResult> mTileFuture;
     private ListenableFuture<Void> mEndFuture;
     private String mWindowOwner;
+    private volatile boolean mCancelled;
 
     static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
@@ -159,9 +161,11 @@
      * @return a future ImageTile set containing the result
      */
     ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
+        mCancelled = false;
         return CallbackToFutureAdapter.getFuture(completer -> {
             mCaptureCompleter = completer;
             mWindowOwner = response.getPackageName();
+            mCaptureCompleter.addCancellationListener(this::onCancelled, mBgExecutor);
             mBgExecutor.execute(() -> {
                 float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
                         SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
@@ -172,6 +176,24 @@
         });
     }
 
+    /**
+     * The ListenableFuture for the long screenshot was cancelled. Be sure to cancel all downstream
+     * futures that might be pending.
+     */
+    private void onCancelled() {
+        mCancelled = true;
+        if (mSessionFuture != null) {
+            mSessionFuture.cancel(true);
+        }
+        if (mTileFuture != null) {
+            mTileFuture.cancel(true);
+        }
+        if (mSession != null) {
+            mSession.end();
+        }
+        mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
+    }
+
     private void onStartComplete() {
         try {
             mSession = mSessionFuture.get();
@@ -192,18 +214,21 @@
         if (LogConfig.DEBUG_SCROLL) {
             Log.d(TAG, "requestNextTile: " + topPx);
         }
+        if (mCancelled) {
+            Log.d(TAG, "requestNextTile: CANCELLED");
+            return;
+        }
         mTileFuture = mSession.requestTile(topPx);
         mTileFuture.addListener(() -> {
             try {
-                if (LogConfig.DEBUG_SCROLL) {
-                    Log.d(TAG, "onCaptureResult");
-                }
                 onCaptureResult(mTileFuture.get());
+            } catch (CancellationException e) {
+                Log.e(TAG, "requestTile cancelled");
             } catch (InterruptedException | ExecutionException e) {
                 Log.e(TAG, "requestTile failed!", e);
                 mCaptureCompleter.setException(e);
             }
-        }, mContext.getMainExecutor());
+        }, mBgExecutor);
     }
 
     private void onCaptureResult(CaptureResult result) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 19876ba..cbb3aba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -446,7 +446,7 @@
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
                 for (RankedListener rl : new ArrayList<>(mListeners)) {
-                    rl.mListener.onFullscreenStateChanged(isFullscreen, true /* isImmersive */);
+                    rl.mListener.onFullscreenStateChanged(isFullscreen);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 6730afa..0352212 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.phone.SystemUIHostDialogProvider;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
@@ -253,11 +254,16 @@
             @Main Executor mainExecutor,
             IActivityManager iActivityManager,
             OngoingCallLogger logger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            StatusBarWindowController statusBarWindowController) {
+        Optional<StatusBarWindowController> windowController =
+                featureFlags.isOngoingCallInImmersiveEnabled()
+                        ? Optional.of(statusBarWindowController)
+                        : Optional.empty();
         OngoingCallController ongoingCallController =
                 new OngoingCallController(
                         notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
-                        iActivityManager, logger, dumpManager);
+                        iActivityManager, logger, dumpManager, windowController);
         ongoingCallController.init();
         return ongoingCallController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index f1d5e02..a090ac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -126,6 +126,19 @@
         mExpansionCallbacks.add(expansionCallback);
     }
 
+    /**
+     * Enable/disable only the back button
+     */
+    public void setBackButtonEnabled(boolean enabled) {
+        int vis = mContainer.getSystemUiVisibility();
+        if (enabled) {
+            vis &= ~View.STATUS_BAR_DISABLE_BACK;
+        } else {
+            vis |= View.STATUS_BAR_DISABLE_BACK;
+        }
+        mContainer.setSystemUiVisibility(vis);
+    }
+
     public void show(boolean resetSecuritySelection) {
         show(resetSecuritySelection, true /* scrimmed */);
     }
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 2e7f4b8b..c34e7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -34,6 +34,9 @@
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_CLOSED;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPEN;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPENING;
 
 import static java.lang.Float.isNaN;
 
@@ -57,7 +60,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
@@ -80,6 +82,7 @@
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
@@ -332,6 +335,7 @@
     private IdleHostViewController mIdleHostViewController;
     private LockIconViewController mLockIconViewController;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    private NotificationsQSContainerController mNotificationsQSContainerController;
     private boolean mAnimateNextPositionUpdate;
     private float mQuickQsOffsetHeight;
     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -730,6 +734,7 @@
             ConversationNotificationManager conversationNotificationManager,
             MediaHierarchyManager mediaHierarchyManager,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            NotificationsQSContainerController notificationsQSContainerController,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
@@ -787,6 +792,8 @@
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
         mMediaHierarchyManager = mediaHierarchyManager;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mNotificationsQSContainerController = notificationsQSContainerController;
+        mNotificationsQSContainerController.init();
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
@@ -1113,7 +1120,7 @@
         constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
         constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
         constraintSet.applyTo(mNotificationContainerParent);
-        mNotificationContainerParent.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
+        mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
 
         updateKeyguardStatusViewAlignment(/* animate= */false);
 
@@ -1940,15 +1947,6 @@
         return !mQsTouchAboveFalsingThreshold;
     }
 
-    /**
-     * Percentage of panel expansion offset, caused by pulling down on a heads-up.
-     */
-    @Override
-    public void setMinFraction(float minFraction) {
-        mMinFraction = minFraction;
-        mDepthController.setPanelPullDownMinFraction(mMinFraction);
-    }
-
     private float computeQsExpansionFraction() {
         if (mQSAnimatingHiddenFromCollapsed) {
             // When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -2248,7 +2246,7 @@
             requestPanelHeightUpdate();
             mFalsingCollector.setQsExpanded(expanded);
             mStatusBar.setQsExpanded(expanded);
-            mNotificationContainerParent.setQsExpanded(expanded);
+            mNotificationsQSContainerController.setQsExpanded(expanded);
             mPulseExpansionHandler.setQsExpanded(expanded);
             mKeyguardBypassController.setQSExpanded(expanded);
             mStatusBarKeyguardViewManager.setQsExpanded(expanded);
@@ -2374,12 +2372,6 @@
         updateQSExpansionEnabledAmbient();
     }
 
-    @Override
-    public void setIsShadeOpening(boolean opening) {
-        mAmbientState.setIsShadeOpening(opening);
-        updateQSExpansionEnabledAmbient();
-    }
-
     private void updateQSExpansionEnabledAmbient() {
         final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsOffsetHeight;
         mQsExpansionEnabledAmbient = mShouldUseSplitNotificationShade
@@ -2855,8 +2847,9 @@
      * @return Whether we should intercept a gesture to open Quick Settings.
      */
     private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
-        if (!isQsExpansionEnabled() || mCollapsedOnDown || (mKeyguardShowing
-                && mKeyguardBypassController.getBypassEnabled())) {
+        if (!isQsExpansionEnabled() || mCollapsedOnDown
+                || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())
+                || (mKeyguardShowing && mShouldUseSplitNotificationShade)) {
             return false;
         }
         View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
@@ -3491,8 +3484,14 @@
         return mBarState == KEYGUARD;
     }
 
+    /**
+     * Sets the minimum fraction for the panel expansion offset. This may be non-zero in certain
+     * cases, such as if there's a heads-up notification.
+     */
     public void setPanelScrimMinFraction(float minFraction) {
         mBar.onPanelMinFractionChanged(minFraction);
+        mMinFraction = minFraction;
+        mDepthController.setPanelPullDownMinFraction(mMinFraction);
     }
 
     public void clearNotificationEffects() {
@@ -4815,4 +4814,26 @@
             return insets;
         }
     }
+
+    private final PanelBar.PanelStateChangeListener mPanelStateChangeListener =
+            new PanelBar.PanelStateChangeListener() {
+
+                @PanelBar.PanelState
+                private int mCurrentState = STATE_CLOSED;
+
+                @Override
+                public void onStateChanged(@PanelBar.PanelState int state) {
+                    mAmbientState.setIsShadeOpening(state == STATE_OPENING);
+                    updateQSExpansionEnabledAmbient();
+
+                    if (state == STATE_OPEN && mCurrentState != state) {
+                        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+                    }
+                    mCurrentState = state;
+                }
+            };
+
+    public PanelBar.PanelStateChangeListener getPanelStateChangeListener() {
+        return mPanelStateChangeListener;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 03d0bb0..9d06d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -171,6 +171,13 @@
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
     }
 
+    /**
+     * @return Location where to place the KeyguardBouncer
+     */
+    public ViewGroup getBouncerContainer() {
+        return mView.findViewById(R.id.keyguard_bouncer_container);
+    }
+
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
new file mode 100644
index 0000000..34bb6d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -0,0 +1,141 @@
+package com.android.systemui.statusbar.phone
+
+import android.view.WindowInsets
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.util.ViewController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+class NotificationsQSContainerController @Inject constructor(
+    view: NotificationsQuickSettingsContainer,
+    private val navigationModeController: NavigationModeController,
+    private val overviewProxyService: OverviewProxyService
+) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
+
+    var qsExpanded = false
+        set(value) {
+            if (field != value) {
+                field = value
+                mView.invalidate()
+            }
+        }
+    var splitShadeEnabled = false
+        set(value) {
+            if (field != value) {
+                field = value
+                // in case device configuration changed while showing QS details/customizer
+                updateBottomSpacing()
+            }
+        }
+
+    private var isQSDetailShowing = false
+    private var isQSCustomizing = false
+    private var isQSCustomizerAnimating = false
+
+    private var notificationsBottomMargin = 0
+    private var bottomStableInsets = 0
+    private var bottomCutoutInsets = 0
+
+    private var isGestureNavigation = true
+    private var taskbarVisible = false
+    private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener {
+        override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+            taskbarVisible = visible
+        }
+    }
+    private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets ->
+        // when taskbar is visible, stableInsetBottom will include its height
+        bottomStableInsets = insets.stableInsetBottom
+        bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0
+        updateBottomSpacing()
+    }
+
+    override fun onInit() {
+        val currentMode: Int = navigationModeController.addListener { mode: Int ->
+            isGestureNavigation = QuickStepContract.isGesturalMode(mode)
+        }
+        isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+    }
+
+    public override fun onViewAttached() {
+        notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+        overviewProxyService.addCallback(taskbarVisibilityListener)
+        mView.setInsetsChangedListener(windowInsetsListener)
+        mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
+    }
+
+    override fun onViewDetached() {
+        overviewProxyService.removeCallback(taskbarVisibilityListener)
+        mView.removeOnInsetsChangedListener()
+        mView.removeQSFragmentAttachedListener()
+    }
+
+    override fun setCustomizerAnimating(animating: Boolean) {
+        if (isQSCustomizerAnimating != animating) {
+            isQSCustomizerAnimating = animating
+            mView.invalidate()
+        }
+    }
+
+    override fun setCustomizerShowing(showing: Boolean) {
+        isQSCustomizing = showing
+        updateBottomSpacing()
+    }
+
+    override fun setDetailShowing(showing: Boolean) {
+        isQSDetailShowing = showing
+        updateBottomSpacing()
+    }
+
+    private fun updateBottomSpacing() {
+        val (containerPadding, notificationsMargin) = calculateBottomSpacing()
+        var qsScrollPaddingBottom = 0
+        if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation ||
+                        taskbarVisible)) {
+            // no taskbar, portrait, navigation buttons enabled:
+            // padding is needed so QS can scroll up over bottom insets - to reach the point when
+            // the whole QS is above bottom insets
+            qsScrollPaddingBottom = bottomStableInsets
+        }
+        mView.setPadding(0, 0, 0, containerPadding)
+        mView.setNotificationsMarginBottom(notificationsMargin)
+        mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+    }
+
+    private fun calculateBottomSpacing(): Pair<Int, Int> {
+        val containerPadding: Int
+        var stackScrollMargin = notificationsBottomMargin
+        if (splitShadeEnabled) {
+            if (isGestureNavigation) {
+                // only default cutout padding, taskbar always hides
+                containerPadding = bottomCutoutInsets
+            } else if (taskbarVisible) {
+                // navigation buttons + visible taskbar means we're NOT on homescreen
+                containerPadding = bottomStableInsets
+            } else {
+                // navigation buttons + hidden taskbar means we're on homescreen
+                containerPadding = 0
+                // we need extra margin for notifications as navigation buttons are below them
+                stackScrollMargin = bottomStableInsets + notificationsBottomMargin
+            }
+        } else {
+            if (isQSCustomizing || isQSDetailShowing) {
+                // Clear out bottom paddings/margins so the qs customization can be full height.
+                containerPadding = 0
+                stackScrollMargin = 0
+            } else if (isGestureNavigation) {
+                containerPadding = bottomCutoutInsets
+            } else if (taskbarVisible) {
+                containerPadding = bottomStableInsets
+            } else {
+                containerPadding = 0
+            }
+        }
+        return containerPadding to stackScrollMargin
+    }
+}
\ No newline at end of file
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 68e28cd..9210a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -33,6 +33,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.function.Consumer;
 
 /**
  * The container with notification stack scroller and quick settings inside.
@@ -43,17 +44,15 @@
     private View mQsFrame;
     private View mStackScroller;
     private View mKeyguardStatusBar;
-    private boolean mQsExpanded;
-    private boolean mCustomizerAnimating;
-    private boolean mCustomizing;
-    private boolean mDetailShowing;
 
-    private int mBottomPadding;
     private int mStackScrollerMargin;
     private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
     private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
     private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
-    private boolean mSplitShadeEnabled;
+    private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
+    private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
+    private QS mQs;
+    private View mQSScrollView;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -69,6 +68,59 @@
     }
 
     @Override
+    public void onFragmentViewCreated(String tag, Fragment fragment) {
+        mQs = (QS) fragment;
+        mQSFragmentAttachedListener.accept(mQs);
+        mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
+    }
+
+    @Override
+    public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
+        invalidate();
+    }
+
+    public void setNotificationsMarginBottom(int margin) {
+        LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+        params.bottomMargin = margin;
+        mStackScroller.setLayoutParams(params);
+    }
+
+    public void setQSScrollPaddingBottom(int paddingBottom) {
+        if (mQSScrollView != null) {
+            mQSScrollView.setPaddingRelative(
+                    mQSScrollView.getPaddingLeft(),
+                    mQSScrollView.getPaddingTop(),
+                    mQSScrollView.getPaddingRight(),
+                    paddingBottom
+            );
+        }
+    }
+
+    public int getDefaultNotificationsMarginBottom() {
+        return mStackScrollerMargin;
+    }
+
+    public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
+        mInsetsChangedListener = onInsetsChangedListener;
+    }
+
+    public void removeOnInsetsChangedListener() {
+        mInsetsChangedListener = insets -> {};
+    }
+
+    public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) {
+        mQSFragmentAttachedListener = qsFragmentAttachedListener;
+        // listener might be attached after fragment is attached
+        if (mQs != null) {
+            mQSFragmentAttachedListener.accept(mQs);
+        }
+    }
+
+    public void removeQSFragmentAttachedListener() {
+        mQSFragmentAttachedListener = qs -> {};
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         FragmentHostManager.get(this).addTagListener(QS.TAG, this);
@@ -82,8 +134,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mBottomPadding = insets.getStableInsetBottom();
-        setPadding(0, 0, 0, mBottomPadding);
+        mInsetsChangedListener.accept(insets);
         return insets;
     }
 
@@ -119,66 +170,4 @@
         }
     }
 
-    @Override
-    public void onFragmentViewCreated(String tag, Fragment fragment) {
-        QS container = (QS) fragment;
-        container.setContainer(this);
-    }
-
-    public void setQsExpanded(boolean expanded) {
-        if (mQsExpanded != expanded) {
-            mQsExpanded = expanded;
-            invalidate();
-        }
-    }
-
-    public void setCustomizerAnimating(boolean isAnimating) {
-        if (mCustomizerAnimating != isAnimating) {
-            mCustomizerAnimating = isAnimating;
-            invalidate();
-        }
-    }
-
-    public void setCustomizerShowing(boolean isShowing) {
-        mCustomizing = isShowing;
-        updateBottomMargin();
-    }
-
-    public void setDetailShowing(boolean isShowing) {
-        mDetailShowing = isShowing;
-        updateBottomMargin();
-    }
-
-    /**
-     * Sets if split shade is enabled and adjusts margins/paddings depending on QS details and
-     * customizer state
-     */
-    public void setSplitShadeEnabled(boolean splitShadeEnabled) {
-        mSplitShadeEnabled = splitShadeEnabled;
-        // in case device was rotated while showing QS details/customizer
-        updateBottomMargin();
-    }
-
-    private void updateBottomMargin() {
-        // in split shade, QS state changes should not influence notifications panel
-        if (!mSplitShadeEnabled && (mCustomizing || mDetailShowing)) {
-            // Clear out bottom paddings/margins so the qs customization can be full height.
-            setPadding(0, 0, 0, 0);
-            setBottomMargin(mStackScroller, 0);
-        } else {
-            setPadding(0, 0, 0, mBottomPadding);
-            setBottomMargin(mStackScroller, mStackScrollerMargin);
-        }
-    }
-
-    private void setBottomMargin(View v, int bottomMargin) {
-        LayoutParams params = (LayoutParams) v.getLayoutParams();
-        params.bottomMargin = bottomMargin;
-        v.setLayoutParams(params);
-    }
-
-    @Override
-    public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
-        invalidate();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 247ede9..1f1090d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.phone;
 
 import static java.lang.Float.isNaN;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.annotation.CallSuper;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -27,6 +28,10 @@
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+
 public abstract class PanelBar extends FrameLayout {
     public static final boolean DEBUG = false;
     public static final String TAG = PanelBar.class.getSimpleName();
@@ -40,26 +45,27 @@
         Log.v(TAG, String.format(fmt, args));
     }
 
+    /** Enum for the current state of the panel. */
+    @Retention(SOURCE)
+    @IntDef({STATE_CLOSED, STATE_OPENING, STATE_OPEN})
+    @interface PanelState {}
     public static final int STATE_CLOSED = 0;
     public static final int STATE_OPENING = 1;
     public static final int STATE_OPEN = 2;
 
-    PanelViewController mPanel;
+    private PanelViewController mPanel;
+    @Nullable private PanelStateChangeListener mPanelStateChangeListener;
     private int mState = STATE_CLOSED;
     private boolean mTracking;
 
-    public void go(int state) {
+    private void go(@PanelState int state) {
         if (DEBUG) LOG("go state: %d -> %d", mState, state);
         mState = state;
-        if (mPanel != null) {
-            mPanel.setIsShadeOpening(state == STATE_OPENING);
+        if (mPanelStateChangeListener != null) {
+            mPanelStateChangeListener.onStateChanged(state);
         }
     }
 
-    protected boolean isShadeOpening() {
-        return mState == STATE_OPENING;
-    }
-
     @Override
     protected Parcelable onSaveInstanceState() {
         Bundle bundle = new Bundle();
@@ -97,6 +103,11 @@
         pv.setBar(this);
     }
 
+    /** Sets the listener that will be notified of panel state changes. */
+    public void setPanelStateChangeListener(PanelStateChangeListener listener) {
+        mPanelStateChangeListener = listener;
+    }
+
     public boolean panelEnabled() {
         return true;
     }
@@ -137,10 +148,7 @@
     /**
      * Percentage of panel expansion offset, caused by pulling down on a heads-up.
      */
-    @CallSuper
-    public void onPanelMinFractionChanged(float minFraction) {
-        mPanel.setMinFraction(minFraction);
-    }
+    abstract void onPanelMinFractionChanged(float minFraction);
 
     /**
      * @param frac the fraction from the expansion in [0, 1]
@@ -166,7 +174,6 @@
         }
         if (fullyOpened && !mTracking) {
             go(STATE_OPEN);
-            onPanelFullyOpened();
         } else if (fullyClosed && !mTracking && mState != STATE_CLOSED) {
             go(STATE_CLOSED);
             onPanelCollapsed();
@@ -207,10 +214,6 @@
         if (DEBUG) LOG("onPanelCollapsed");
     }
 
-    public void onPanelFullyOpened() {
-        if (DEBUG) LOG("onPanelFullyOpened");
-    }
-
     public void onTrackingStarted() {
         mTracking = true;
     }
@@ -226,4 +229,10 @@
     public void onClosingFinished() {
 
     }
+
+    /** An interface that will be notified of panel state changes. */
+    public interface PanelStateChangeListener {
+        /** Called when the state changes. */
+        void onStateChanged(@PanelState int state);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index b215515..768567b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -343,13 +343,6 @@
     protected abstract float getOpeningHeight();
 
     /**
-     * Minimum fraction from where expansion should start. This is set when pulling down on a
-     * heads-up notification.
-     * @param minFraction Fraction from 0 to 1.
-     */
-    public abstract void setMinFraction(float minFraction);
-
-    /**
      * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
      * horizontal direction
      */
@@ -1171,11 +1164,6 @@
         return new OnConfigurationChangedListener();
     }
 
-    /**
-     * Set that the panel is currently opening and not fully opened or closed.
-     */
-    public abstract void setIsShadeOpening(boolean opening);
-
     public class TouchHandler implements View.OnTouchListener {
         public boolean onInterceptTouchEvent(MotionEvent event) {
             if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 9ab6cdd..1cca477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -56,7 +56,6 @@
 
     StatusBar mBar;
 
-    boolean mIsFullyOpenedPanel = false;
     private ScrimController mScrimController;
     private float mMinFraction;
     private Runnable mHideExpandedRunnable = new Runnable() {
@@ -216,7 +215,6 @@
         super.onPanelCollapsed();
         // Close the status bar in the next frame so we can show the end of the animation.
         post(mHideExpandedRunnable);
-        mIsFullyOpenedPanel = false;
     }
 
     public void removePendingHideExpandedRunnables() {
@@ -224,15 +222,6 @@
     }
 
     @Override
-    public void onPanelFullyOpened() {
-        super.onPanelFullyOpened();
-        if (!mIsFullyOpenedPanel) {
-            mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-        }
-        mIsFullyOpenedPanel = true;
-    }
-
-    @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean barConsumedEvent = mBar.interceptTouchEvent(event);
 
@@ -283,7 +272,6 @@
         if (isNaN(minFraction)) {
             throw new IllegalArgumentException("minFraction cannot be NaN");
         }
-        super.onPanelMinFractionChanged(minFraction);
         if (mMinFraction != minFraction) {
             mMinFraction = minFraction;
             updateScrimFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 06c3573..bfb4b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -628,6 +628,7 @@
     private final Executor mUiBgExecutor;
 
     protected boolean mDozing;
+    private boolean mIsFullscreen;
 
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -909,6 +910,9 @@
         mActivityLaunchAnimator = activityLaunchAnimator;
         mDialogLaunchAnimator = dialogLaunchAnimator;
 
+        // The status bar background may need updating when the ongoing call status changes.
+        mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode());
+
         // TODO(b/190746471): Find a better home for this.
         DateTimeView.setReceiverHandler(timeTickHandler);
 
@@ -1161,6 +1165,8 @@
                     mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
                     mStatusBarView.setBar(this);
                     mStatusBarView.setPanel(mNotificationPanelViewController);
+                    mStatusBarView.setPanelStateChangeListener(
+                            mNotificationPanelViewController.getPanelStateChangeListener());
                     mStatusBarView.setScrimController(mScrimController);
                     mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
                     for (ExpansionChangedListener listener : mExpansionChangedListeners) {
@@ -1550,6 +1556,9 @@
                     time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
             where.getLocationInWindow(mTmpInt2);
+
+            // NOTE, the incoming view can sometimes be the entire container... unsure if
+            // this location is valuable enough
             mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
                     mTmpInt2[1] + where.getHeight() / 2);
             mFalsingCollector.onScreenOnFromTouch();
@@ -1643,7 +1652,7 @@
                     }
                 });
         mStatusBarKeyguardViewManager.registerStatusBar(
-                /* statusBar= */ this, getBouncerContainer(),
+                /* statusBar= */ this,
                 mNotificationPanelViewController, mBiometricUnlockController,
                 mStackScroller, mKeyguardBypassController);
         mKeyguardIndicationController
@@ -1678,8 +1687,8 @@
         return mNotificationPanelViewController;
     }
 
-    protected ViewGroup getBouncerContainer() {
-        return mNotificationShadeWindowView.findViewById(R.id.keyboard_bouncer_container);
+    public ViewGroup getBouncerContainer() {
+        return mNotificationShadeWindowViewController.getBouncerContainer();
     }
 
     public int getStatusBarHeight() {
@@ -2237,7 +2246,7 @@
         if (!mTransientShown) {
             mTransientShown = true;
             mNoAnimationOnNextBarModeChange = true;
-            handleTransientChanged();
+            maybeUpdateBarMode();
         }
     }
 
@@ -2245,11 +2254,11 @@
     void clearTransient() {
         if (mTransientShown) {
             mTransientShown = false;
-            handleTransientChanged();
+            maybeUpdateBarMode();
         }
     }
 
-    private void handleTransientChanged() {
+    private void maybeUpdateBarMode() {
         final int barMode = barMode(mTransientShown, mAppearance);
         if (updateBarMode(barMode)) {
             mLightBarController.onStatusBarModeChanged(barMode);
@@ -2267,9 +2276,11 @@
         return false;
     }
 
-    private static @TransitionMode int barMode(boolean isTransient, int appearance) {
+    private @TransitionMode int barMode(boolean isTransient, int appearance) {
         final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
-        if (isTransient) {
+        if (mOngoingCallController.hasOngoingCall() && mIsFullscreen) {
+            return MODE_SEMI_TRANSPARENT;
+        } else if (isTransient) {
             return MODE_SEMI_TRANSPARENT;
         } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
             return MODE_LIGHTS_OUT;
@@ -4471,6 +4482,12 @@
                     updateReportRejectedTouchVisibility();
                     Trace.endSection();
                 }
+
+                @Override
+                public void onFullscreenStateChanged(boolean isFullscreen) {
+                    mIsFullscreen = isFullscreen;
+                    maybeUpdateBarMode();
+                }
             };
 
     private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 18fd9d3..07489a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -120,7 +120,8 @@
         @Override
         public void onFullyShown() {
             updateStates();
-            mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mContainer, "BOUNCER_VISIBLE");
+            mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(),
+                    mStatusBar.getBouncerContainer(), "BOUNCER_VISIBLE");
         }
 
         @Override
@@ -174,7 +175,6 @@
     private NotificationPanelViewController mNotificationPanelViewController;
     private BiometricUnlockController mBiometricUnlockController;
 
-    private ViewGroup mContainer;
     private View mNotificationContainer;
 
     protected KeyguardBouncer mBouncer;
@@ -270,14 +270,14 @@
 
     @Override
     public void registerStatusBar(StatusBar statusBar,
-            ViewGroup container,
             NotificationPanelViewController notificationPanelViewController,
             BiometricUnlockController biometricUnlockController,
             View notificationContainer,
             KeyguardBypassController bypassController) {
         mStatusBar = statusBar;
-        mContainer = container;
         mBiometricUnlockController = biometricUnlockController;
+
+        ViewGroup container = mStatusBar.getBouncerContainer();
         mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
         mNotificationPanelViewController = notificationPanelViewController;
         notificationPanelViewController.addExpansionListener(this);
@@ -356,7 +356,8 @@
         } else if (mPulsing && expansion == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
             // unlocked.) Let's simply wake-up to dismiss the lock screen.
-            mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mContainer, "BOUNCER_VISIBLE");
+            mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mStatusBar.getBouncerContainer(),
+                    "BOUNCER_VISIBLE");
         }
     }
 
@@ -833,7 +834,7 @@
     }
 
     public void onKeyguardFadedAway() {
-        mContainer.postDelayed(() -> mNotificationShadeWindowController
+        mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
                         .setKeyguardFadingAway(false), 100);
         ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
         mStatusBar.finishKeyguardFadingAway();
@@ -958,10 +959,6 @@
     };
 
     protected void updateStates() {
-        if (mContainer == null ) {
-            return;
-        }
-        int vis = mContainer.getSystemUiVisibility();
         boolean showing = mShowing;
         boolean occluded = mOccluded;
         boolean bouncerShowing = mBouncer.isShowing();
@@ -973,9 +970,9 @@
                 (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
             if (bouncerDismissible || !showing || remoteInputActive) {
-                mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK);
+                mBouncer.setBackButtonEnabled(true);
             } else {
-                mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK);
+                mBouncer.setBackButtonEnabled(false);
             }
         }
 
@@ -1042,11 +1039,11 @@
                 if (delay == 0) {
                     mMakeNavigationBarVisibleRunnable.run();
                 } else {
-                    mContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
+                    mNotificationContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
                             delay);
                 }
             } else {
-                mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
+                mNotificationContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
                 mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
                         .hide(navigationBars());
             }
@@ -1371,4 +1368,4 @@
         void requestUdfps(boolean requestUdfps, int color);
 
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index aec27d0..b708861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -208,6 +208,12 @@
         apply(mCurrentState);
     }
 
+    /** Sets whether there is currently an ongoing call. */
+    public void setIsCallOngoing(boolean isCallOngoing) {
+        mCurrentState.mIsCallOngoing = isCallOngoing;
+        apply(mCurrentState);
+    }
+
     /**
      * Return the container in which we should run launch animations started from the status bar and
      * expanding into the opening window.
@@ -248,10 +254,13 @@
     private static class State {
         boolean mForceStatusBarVisible;
         boolean mIsLaunchAnimationRunning;
+        boolean mIsCallOngoing;
     }
 
     private void applyForceStatusBarVisibleFlag(State state) {
-        if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) {
+        if (state.mForceStatusBarVisible
+                || state.mIsLaunchAnimationRunning
+                || state.mIsCallOngoing) {
             mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
         } else {
             mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index ecf3b86..2791678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -29,9 +29,9 @@
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
 import com.android.systemui.statusbar.phone.TapAgainView;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -67,8 +67,8 @@
     @Provides
     @StatusBarComponent.StatusBarScope
     public static NotificationStackScrollLayout providesNotificationStackScrollLayout(
-            NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
-        return notificationStackScrollLayoutController.getView();
+            NotificationShadeWindowView notificationShadeWindowView) {
+        return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller);
     }
 
     /** */
@@ -149,4 +149,12 @@
     public static TapAgainView getTapAgainView(NotificationPanelView npv) {
         return npv.getTapAgainView();
     }
+
+    /** */
+    @Provides
+    @StatusBarComponent.StatusBarScope
+    public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer(
+            NotificationShadeWindowView notificationShadeWindowView) {
+        return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index ab6ee89..b7c80de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -37,10 +37,12 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.StatusBarWindowController
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.util.time.SystemClock
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -57,6 +59,7 @@
     private val iActivityManager: IActivityManager,
     private val logger: OngoingCallLogger,
     private val dumpManager: DumpManager,
+    private val statusBarWindowController: Optional<StatusBarWindowController>,
 ) : CallbackController<OngoingCallListener>, Dumpable {
 
     /** Non-null if there's an active call notification. */
@@ -201,7 +204,7 @@
             }
 
             setUpUidObserver(currentCallNotificationInfo)
-
+            statusBarWindowController.ifPresent { it.setIsCallOngoing(true) }
             mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
         } else {
             // If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -268,6 +271,7 @@
     private fun removeChip() {
         callNotificationInfo = null
         tearDownChipView()
+        statusBarWindowController.ifPresent { it.setIsCallOngoing(false) }
         mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
         if (uidObserver != null) {
             iActivityManager.unregisterUidObserver(uidObserver)
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 0ba072e..f97f4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -31,7 +31,6 @@
 
 public class NotificationChannels extends SystemUI {
     public static String ALERTS      = "ALR";
-    public static String SCREENSHOTS_LEGACY = "SCN";
     public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
     public static String GENERAL     = "GEN";
     public static String STORAGE     = "DSK";
@@ -84,18 +83,11 @@
                 general,
                 storage,
                 createScreenshotChannel(
-                        context.getString(R.string.notification_channel_screenshot),
-                        nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
+                        context.getString(R.string.notification_channel_screenshot)),
                 batteryChannel,
                 hint
         ));
 
-        // Delete older SS channel if present.
-        // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
-        // This line can be deleted in Q.
-        nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
-
-
         if (isTv(context)) {
             // TV specific notification channel for TV PIP controls.
             // Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
@@ -113,7 +105,7 @@
      * @return
      */
     @VisibleForTesting static NotificationChannel createScreenshotChannel(
-            String name, NotificationChannel legacySS) {
+            String name) {
         NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
                 name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
 
@@ -121,24 +113,6 @@
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
         screenshotChannel.setBlockable(true);
 
-        if (legacySS != null) {
-            // Respect any user modified fields from the old channel.
-            int userlock = legacySS.getUserLockedFields();
-            if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) {
-                screenshotChannel.setImportance(legacySS.getImportance());
-            }
-            if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0)  {
-                screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes());
-            }
-            if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0)  {
-                screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern());
-            }
-            if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0)  {
-                screenshotChannel.setLightColor(legacySS.getLightColor());
-            }
-            // skip show_badge, irrelevant for system channel
-        }
-
         return screenshotChannel;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index 2c01a70..db2aca8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -40,8 +40,8 @@
         this.wallpaperInfo = wallpaperInfo
     }
 
-    private val shouldUseDefaultDisplayStateChangeTransition: Boolean
-        get() = wallpaperInfo?.shouldUseDefaultDisplayStateChangeTransition()
+    private val shouldUseDefaultUnfoldTransition: Boolean
+        get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
             ?: true
 
     fun setNotificationShadeZoom(zoomOut: Float) {
@@ -50,7 +50,7 @@
     }
 
     fun setUnfoldTransitionZoom(zoomOut: Float) {
-        if (shouldUseDefaultDisplayStateChangeTransition) {
+        if (shouldUseDefaultUnfoldTransition) {
             unfoldTransitionZoomOut = zoomOut
             updateZoom()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
index 28cba8e..40982bb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
@@ -18,8 +18,7 @@
 
 import android.util.Log;
 
-import androidx.annotation.NonNull;
-
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
@@ -42,9 +41,9 @@
     PostureDependentProximitySensor(
             @PrimaryProxSensor ThresholdSensor[] postureToPrimaryProxSensorMap,
             @SecondaryProxSensor ThresholdSensor[] postureToSecondaryProxSensorMap,
-            @NonNull DelayableExecutor delayableExecutor,
-            @NonNull Execution execution,
-            @NonNull DevicePostureController devicePostureController
+            @Main DelayableExecutor delayableExecutor,
+            Execution execution,
+            DevicePostureController devicePostureController
     ) {
         super(
                 postureToPrimaryProxSensorMap[0],
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ff26310..514a903 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -73,6 +73,7 @@
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.startingsurface.StartingWindowController;
 import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
@@ -81,10 +82,14 @@
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
 
 import java.util.Optional;
 
+import javax.inject.Provider;
+
 import dagger.BindsOptionalOf;
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -234,16 +239,48 @@
     static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
             Context context,
             Optional<ShellUnfoldProgressProvider> progressProvider,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            Lazy<UnfoldBackgroundController> unfoldBackgroundController,
             DisplayInsetsController displayInsetsController,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return progressProvider.map(shellUnfoldTransitionProgressProvider ->
                 new FullscreenUnfoldController(context, mainExecutor,
-                        shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer,
+                        unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider,
                         displayInsetsController));
     }
 
+    @Provides
+    static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
+            Optional<ShellUnfoldProgressProvider> progressProvider,
+            Context context,
+            TransactionPool transactionPool,
+            Lazy<UnfoldBackgroundController> unfoldBackgroundController,
+            DisplayInsetsController displayInsetsController,
+            @ShellMainThread ShellExecutor mainExecutor
+    ) {
+        return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+                new StageTaskUnfoldController(
+                        context,
+                        transactionPool,
+                        shellUnfoldTransitionProgressProvider,
+                        displayInsetsController,
+                        unfoldBackgroundController.get(),
+                        mainExecutor
+                ));
+    }
+
+    @WMSingleton
+    @Provides
+    static UnfoldBackgroundController provideUnfoldBackgroundController(
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            Context context
+    ) {
+        return new UnfoldBackgroundController(
+                context,
+                rootTaskDisplayAreaOrganizer
+        );
+    }
+
     //
     // Freeform (optional feature)
     //
@@ -401,12 +438,13 @@
             @ShellMainThread ShellExecutor mainExecutor,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool) {
+            TransactionPool transactionPool,
+            Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
         if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
             return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
                     rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
                     displayInsetsController, transitions,
-                    transactionPool));
+                    transactionPool, stageTaskUnfoldControllerProvider));
         } else {
             return Optional.empty();
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 88b596c..5c7885f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -142,7 +142,8 @@
                 mBypassController,
                 mSmartspaceController,
                 mKeyguardUnlockAnimationController,
-                mSmartSpaceTransitionController
+                mSmartSpaceTransitionController,
+                mResources
         );
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
new file mode 100644
index 0000000..87b9172
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -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.systemui.controls.ui
+
+import android.app.PendingIntent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.wm.shell.TaskView
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class DetailDialogTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var taskView: TaskView
+    @Mock
+    private lateinit var controlViewHolder: ControlViewHolder
+    @Mock
+    private lateinit var pendingIntent: PendingIntent
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testPendingIntentIsUnModified() {
+        // GIVEN the dialog is created with a PendingIntent
+        val dialog = createDialog(pendingIntent)
+
+        // WHEN the TaskView is initialized
+        dialog.stateCallback.onInitialized()
+
+        // THEN the PendingIntent used to call startActivity is unmodified by systemui
+        verify(taskView).startActivity(eq(pendingIntent), any(), any(), any())
+    }
+
+    private fun createDialog(pendingIntent: PendingIntent): DetailDialog {
+        return DetailDialog(
+            mContext,
+            taskView,
+            pendingIntent,
+            controlViewHolder
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
new file mode 100644
index 0000000..df11284
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AnimatableClockController;
+import com.android.keyguard.AnimatableClockView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.Utils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AnimatableClockControllerTest extends SysuiTestCase {
+    @Mock
+    private AnimatableClockView mClockView;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private BatteryController mBatteryController;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    private KeyguardBypassController mBypassController;
+    @Mock
+    private Resources mResources;
+
+    private MockitoSession mStaticMockSession;
+    private AnimatableClockController mAnimatableClockController;
+
+    // Capture listeners so that they can be used to send events
+    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+    private View.OnAttachStateChangeListener mAttachListener;
+
+    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
+    private StatusBarStateController.StateListener mStatusBarStateCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStaticMockSession = mockitoSession()
+                .mockStatic(Utils.class)
+                .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
+                .startMocking();
+        when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
+
+        mAnimatableClockController = new AnimatableClockController(
+                mClockView,
+                mStatusBarStateController,
+                mBroadcastDispatcher,
+                mBatteryController,
+                mKeyguardUpdateMonitor,
+                mBypassController,
+                mResources
+        );
+        mAnimatableClockController.init();
+        captureAttachListener();
+    }
+
+    @After
+    public void tearDown() {
+        mStaticMockSession.finishMocking();
+    }
+
+    @Test
+    public void testOnAttachedUpdatesDozeStateToTrue() {
+        // GIVEN dozing
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+        when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+        // WHEN the clock view gets attached
+        mAttachListener.onViewAttachedToWindow(mClockView);
+
+        // THEN the clock controller updated its dozing state to true
+        assertTrue(mAnimatableClockController.isDozing());
+    }
+
+    @Test
+    public void testOnAttachedUpdatesDozeStateToFalse() {
+        // GIVEN not dozing
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+
+        // WHEN the clock view gets attached
+        mAttachListener.onViewAttachedToWindow(mClockView);
+
+        // THEN the clock controller updated its dozing state to false
+        assertFalse(mAnimatableClockController.isDozing());
+    }
+
+    private void captureAttachListener() {
+        verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+        mAttachListener = mAttachCaptor.getValue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
new file mode 100644
index 0000000..175ec87f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.media
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
+private val DATA = MediaData(
+    userId = -1,
+    initialized = false,
+    backgroundColor = 0,
+    app = null,
+    appIcon = null,
+    artist = null,
+    song = null,
+    artwork = null,
+    actions = emptyList(),
+    actionsToShowInCompact = emptyList(),
+    packageName = "INVALID",
+    token = null,
+    clickIntent = null,
+    device = null,
+    active = true,
+    resumeAction = null)
+
+private val SMARTSPACE_KEY = "smartspace"
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class MediaCarouselControllerTest : SysuiTestCase() {
+
+    @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
+    @Mock lateinit var panel: MediaControlPanel
+    @Mock lateinit var visualStabilityManager: VisualStabilityManager
+    @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+    @Mock lateinit var activityStarter: ActivityStarter
+    @Mock @Main private lateinit var executor: DelayableExecutor
+    @Mock lateinit var mediaDataManager: MediaDataManager
+    @Mock lateinit var configurationController: ConfigurationController
+    @Mock lateinit var falsingCollector: FalsingCollector
+    @Mock lateinit var falsingManager: FalsingManager
+    @Mock lateinit var dumpManager: DumpManager
+
+    private val clock = FakeSystemClock()
+    private lateinit var mediaCarouselController: MediaCarouselController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        mediaCarouselController = MediaCarouselController(
+            context,
+            mediaControlPanelFactory,
+            visualStabilityManager,
+            mediaHostStatesManager,
+            activityStarter,
+            clock,
+            executor,
+            mediaDataManager,
+            configurationController,
+            falsingCollector,
+            falsingManager,
+            dumpManager
+        )
+
+        MediaPlayerData.clear()
+    }
+
+    @Test
+    fun testPlayerOrdering() {
+        // Test values: key, data, last active time
+        val playingLocal = Triple("playing local",
+            DATA.copy(active = true, isPlaying = true, isLocalSession = true, resumption = false),
+            4500L)
+
+        val playingRemote = Triple("playing remote",
+            DATA.copy(active = true, isPlaying = true, isLocalSession = false, resumption = false),
+            5000L)
+
+        val pausedLocal = Triple("paused local",
+            DATA.copy(active = true, isPlaying = false, isLocalSession = true, resumption = false),
+            1000L)
+
+        val pausedRemote = Triple("paused remote",
+            DATA.copy(active = true, isPlaying = false, isLocalSession = false, resumption = false),
+            2000L)
+
+        val resume1 = Triple("resume 1",
+            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+            500L)
+
+        val resume2 = Triple("resume 2",
+            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+            1000L)
+
+        // Expected ordering for media players:
+        // Actively playing local sessions
+        // Actively playing remote sessions
+        // Paused sessions, by last active
+        // Resume controls, by last active
+
+        val expected = listOf(playingLocal, playingRemote, pausedRemote, pausedLocal, resume2,
+            resume1)
+
+        expected.forEach {
+            clock.setCurrentTimeMillis(it.third)
+            MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
+                panel, clock)
+        }
+
+        for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
+            assertEquals(expected.get(index).first, key.data.notificationKey)
+        }
+    }
+
+    @Test
+    fun testOrderWithSmartspace_prioritized() {
+        testPlayerOrdering()
+
+        // If smartspace is prioritized
+        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
+            true, clock)
+
+        // Then it should be shown immediately after any actively playing controls
+        assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+    }
+
+    @Test
+    fun testOrderWithSmartspace_notPrioritized() {
+        testPlayerOrdering()
+
+        // If smartspace is not prioritized
+        MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
+            false, clock)
+
+        // Then it should be shown at the end of the carousel
+        val size = MediaPlayerData.playerKeys().size
+        assertTrue(MediaPlayerData.playerKeys().elementAt(size - 1).isSsMediaRec)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 28aed20..a435e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -86,6 +86,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        MediaPlayerData.clear()
         mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
                 lockscreenUserManager, executor, clock)
         mediaDataFilter.mediaDataManager = mediaDataManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index d864aae..2b2fc51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -255,6 +255,7 @@
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
                     eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index e9e965e..8dc9eff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -127,7 +127,7 @@
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(6)
         assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
-            playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote,
+            playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerCanResume,
             playerUndetermined).inOrder()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 1de4647..4b2380a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -345,6 +345,8 @@
     private LockscreenGestureLogger mLockscreenGestureLogger;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private NotificationsQSContainerController mNotificationsQSContainerController;
 
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -479,6 +481,7 @@
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
                 mConversationNotificationManager, mMediaHiearchyManager,
                 mStatusBarKeyguardViewManager,
+                mNotificationsQSContainerController,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
                 mKeyguardQsUserSwitchComponentFactory,
@@ -536,8 +539,8 @@
     }
 
     @Test
-    public void testSetMinFraction() {
-        mNotificationPanelViewController.setMinFraction(0.5f);
+    public void testSetPanelScrimMinFraction() {
+        mNotificationPanelViewController.setPanelScrimMinFraction(0.5f);
         verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
new file mode 100644
index 0000000..337e64592
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -0,0 +1,254 @@
+package com.android.systemui.statusbar.phone
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationQSContainerControllerTest : SysuiTestCase() {
+
+    companion object {
+        const val STABLE_INSET_BOTTOM = 100
+        const val CUTOUT_HEIGHT = 50
+        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+        const val NOTIFICATIONS_MARGIN = 50
+    }
+
+    @Mock
+    private lateinit var navigationModeController: NavigationModeController
+    @Mock
+    private lateinit var overviewProxyService: OverviewProxyService
+    @Mock
+    private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
+    @Captor
+    lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+    @Captor
+    lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+    @Captor
+    lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+
+    private lateinit var notificationsQSContainerController: NotificationsQSContainerController
+    private lateinit var navigationModeCallback: ModeChangedListener
+    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        notificationsQSContainerController = NotificationsQSContainerController(
+                notificationsQSContainer,
+                navigationModeController,
+                overviewProxyService
+        )
+        whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
+                .thenReturn(NOTIFICATIONS_MARGIN)
+        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+                .thenReturn(GESTURES_NAVIGATION)
+        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+        doNothing().`when`(notificationsQSContainer)
+                .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+
+        notificationsQSContainerController.init()
+        notificationsQSContainerController.onViewAttached()
+
+        navigationModeCallback = navigationModeCaptor.value
+        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+        windowInsetsCallback = windowInsetsCallbackCaptor.value
+    }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        given(taskbarVisible = true,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+
+        given(taskbarVisible = true,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade() {
+        // when taskbar is not visible, it means we're on the home screen
+        notificationsQSContainerController.splitShadeEnabled = true
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withCutout())
+        then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withCutout().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        given(taskbarVisible = true,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = true,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withCutout().withStableBottom())
+        then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testCustomizingInSinglePaneShade() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        notificationsQSContainerController.setCustomizerShowing(true)
+        // always sets spacings to 0
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade() {
+        notificationsQSContainerController.splitShadeEnabled = false
+        notificationsQSContainerController.setDetailShowing(true)
+        // always sets spacings to 0
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0,
+                expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSplitShade() {
+        notificationsQSContainerController.splitShadeEnabled = true
+        given(taskbarVisible = false,
+                navigationMode = GESTURES_NAVIGATION,
+                insets = windowInsets().withStableBottom())
+        then(expectedContainerPadding = 0)
+
+        notificationsQSContainerController.setDetailShowing(true)
+        // should not influence spacing
+        given(taskbarVisible = false,
+                navigationMode = BUTTONS_NAVIGATION,
+                insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    private fun given(
+        taskbarVisible: Boolean,
+        navigationMode: Int,
+        insets: WindowInsets
+    ) {
+        Mockito.clearInvocations(notificationsQSContainer)
+        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+        navigationModeCallback.onNavigationModeChanged(navigationMode)
+        windowInsetsCallback.accept(insets)
+    }
+
+    fun then(
+        expectedContainerPadding: Int,
+        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+        expectedQsPadding: Int = 0
+    ) {
+        verify(notificationsQSContainer)
+                .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+        verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
+        verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+        Mockito.clearInvocations(notificationsQSContainer)
+    }
+
+    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+    private fun emptyInsets() = mock(WindowInsets::class.java)
+
+    private fun WindowInsets.withCutout(): WindowInsets {
+        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        return this
+    }
+
+    private fun WindowInsets.withStableBottom(): WindowInsets {
+        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+        return this
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index aee9f12..ec7e07f90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -35,6 +35,8 @@
     private lateinit var panelView: ViewGroup
     @Mock
     private lateinit var scrimController: ScrimController
+    @Mock
+    private lateinit var statusBar: StatusBar
 
     private lateinit var view: PhoneStatusBarView
 
@@ -48,6 +50,7 @@
         view = PhoneStatusBarView(mContext, null)
         view.setPanel(panelViewController)
         view.setScrimController(scrimController)
+        view.setBar(statusBar)
     }
 
     @Test
@@ -72,7 +75,7 @@
 
     @Test
     fun panelExpansionChanged_fracZero_stateChangeListenerNotified() {
-        val listener = TestStateChangedListener()
+        val listener = TestExpansionStateChangedListener()
         view.setPanelExpansionStateChangedListener(listener)
 
         view.panelExpansionChanged(0f, false)
@@ -82,7 +85,7 @@
 
     @Test
     fun panelExpansionChanged_fracOne_stateChangeListenerNotified() {
-        val listener = TestStateChangedListener()
+        val listener = TestExpansionStateChangedListener()
         view.setPanelExpansionStateChangedListener(listener)
 
         view.panelExpansionChanged(1f, false)
@@ -92,7 +95,7 @@
 
     @Test
     fun panelExpansionChanged_fracHalf_stateChangeListenerNotNotified() {
-        val listener = TestStateChangedListener()
+        val listener = TestExpansionStateChangedListener()
         view.setPanelExpansionStateChangedListener(listener)
 
         view.panelExpansionChanged(0.5f, false)
@@ -106,11 +109,59 @@
         // No assert needed, just testing no crash
     }
 
-    private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener {
+    @Test
+    fun panelStateChanged_toStateOpening_listenerNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelStateChangeListener(listener)
+
+        view.panelExpansionChanged(0.5f, true)
+
+        assertThat(listener.state).isEqualTo(PanelBar.STATE_OPENING)
+    }
+
+    @Test
+    fun panelStateChanged_toStateOpen_listenerNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelStateChangeListener(listener)
+
+        view.panelExpansionChanged(1f, true)
+
+        assertThat(listener.state).isEqualTo(PanelBar.STATE_OPEN)
+    }
+
+    @Test
+    fun panelStateChanged_toStateClosed_listenerNotified() {
+        val listener = TestStateChangedListener()
+        view.setPanelStateChangeListener(listener)
+
+        // First, open the panel
+        view.panelExpansionChanged(1f, true)
+
+        // Then, close it again
+        view.panelExpansionChanged(0f, false)
+
+        assertThat(listener.state).isEqualTo(PanelBar.STATE_CLOSED)
+    }
+
+    @Test
+    fun panelStateChanged_noListener_noCrash() {
+        view.panelExpansionChanged(1f, true)
+        // No assert needed, just testing no crash
+    }
+
+    private class TestExpansionStateChangedListener
+        : PhoneStatusBarView.PanelExpansionStateChangedListener {
         var stateChangeCalled: Boolean = false
 
         override fun onPanelExpansionStateChanged() {
             stateChangeCalled = true
         }
     }
+
+    private class TestStateChangedListener : PanelBar.PanelStateChangeListener {
+        var state: Int = 0
+        override fun onStateChanged(state: Int) {
+            this.state = state
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6e3d5ce..3fcd071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,15 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -119,7 +116,7 @@
                 any(ViewGroup.class),
                 any(KeyguardBouncer.BouncerExpansionCallback.class)))
                 .thenReturn(mBouncer);
-
+        when(mStatusBar.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
         mWakefulnessLifecycle = new WakefulnessLifecycle(
                 getContext(),
@@ -143,7 +140,7 @@
                 mUnlockedScreenOffAnimationController,
                 mKeyguardMessageAreaFactory,
                 mShadeController);
-        mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
+        mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar,
                 mNotificationPanelView, mBiometrucUnlockController,
                 mNotificationContainer, mBypassController);
         mStatusBarKeyguardViewManager.show(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 6868e18..741d95f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -459,7 +459,7 @@
                 mock(DumpManager.class),
                 mActivityLaunchAnimator,
                 mDialogLaunchAnimator);
-        when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
+        when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class),
                 any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
                 any(ViewGroup.class), any(KeyguardBypassController.class)))
                 .thenReturn(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index fe7ec68..e97aba2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.StatusBarWindowController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
@@ -60,6 +61,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.Optional
 
 private const val CALL_UID = 900
 
@@ -84,6 +86,7 @@
     @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
     @Mock private lateinit var mockActivityStarter: ActivityStarter
     @Mock private lateinit var mockIActivityManager: IActivityManager
+    @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
 
     private lateinit var chipView: View
 
@@ -108,6 +111,7 @@
                 mockIActivityManager,
                 OngoingCallLogger(uiEventLoggerFake),
                 DumpManager(),
+                Optional.of(mockStatusBarWindowController),
         )
         controller.init()
         controller.addCallback(mockOngoingCallListener)
@@ -134,6 +138,13 @@
     }
 
     @Test
+    fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
+        notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+        verify(mockStatusBarWindowController).setIsCallOngoing(true)
+    }
+
+    @Test
     fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
         notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
 
@@ -224,6 +235,16 @@
         verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
     }
 
+    @Test
+    fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() {
+        val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+        notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+
+        notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+        verify(mockStatusBarWindowController).setIsCallOngoing(false)
+    }
+
     /** Regression test for b/188491504. */
     @Test
     fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
index f1c9980..c7bcdef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
@@ -22,7 +22,6 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Context;
-import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
@@ -68,51 +67,4 @@
         list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId())));
     }
 
-    @Test
-    public void testChannelSetup_noLegacyScreenshot() {
-        // Assert old channel cleaned up.
-        // TODO: remove that code + this test after P.
-        NotificationChannels.createAll(mContext);
-        ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
-        verify(mMockNotificationManager).deleteNotificationChannel(
-                NotificationChannels.SCREENSHOTS_LEGACY);
-    }
-
-    @Test
-    public void testInheritFromLegacy_keepsUserLockedLegacySettings() {
-        NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
-                NotificationManager.IMPORTANCE_MIN);
-        legacyChannel.setImportance(NotificationManager.IMPORTANCE_NONE);;
-        legacyChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
-                legacyChannel.getAudioAttributes());
-        legacyChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE |
-                NotificationChannel.USER_LOCKED_SOUND);
-        NotificationChannel newChannel =
-                NotificationChannels.createScreenshotChannel("newName", legacyChannel);
-        // NONE importance user locked, so don't use HIGH for new channel.
-        assertEquals(NotificationManager.IMPORTANCE_NONE, newChannel.getImportance());
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, newChannel.getSound());
-    }
-
-    @Test
-    public void testInheritFromLegacy_dropsUnlockedLegacySettings() {
-        NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
-                NotificationManager.IMPORTANCE_MIN);
-        NotificationChannel newChannel =
-                NotificationChannels.createScreenshotChannel("newName", legacyChannel);
-        assertEquals(null, newChannel.getSound());
-        assertEquals("newName", newChannel.getName());
-        // MIN importance not user locked, so HIGH wins out.
-        assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
-    }
-
-    @Test
-    public void testInheritFromLegacy_noLegacyExists() {
-        NotificationChannel newChannel =
-                NotificationChannels.createScreenshotChannel("newName", null);
-        assertEquals(null, newChannel.getSound());
-        assertEquals("newName", newChannel.getName());
-        assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
-    }
-
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index b54aadb..d8e418a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -138,7 +138,7 @@
 
     private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo {
         val info = mock(WallpaperInfo::class.java)
-        whenever(info.shouldUseDefaultDisplayStateChangeTransition())
+        whenever(info.shouldUseDefaultUnfoldTransition())
             .thenReturn(useDefaultTransition)
         return info
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index b0893cc..ed37d7e 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.graphics.Camera;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.hardware.camera2.CameraAccessException;
@@ -135,6 +136,7 @@
 
     private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>();
     private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+    private CameraManager mCameraManager;
 
     private static boolean checkForAdvancedAPI() {
         if (EXTENSIONS_PRESENT && EXTENSIONS_VERSION.startsWith(ADVANCED_VERSION_PREFIX)) {
@@ -460,12 +462,12 @@
         // This will setup the camera vendor tag descriptor in the service process
         // along with all camera characteristics.
         try {
-            CameraManager manager = getSystemService(CameraManager.class);
+            mCameraManager = getSystemService(CameraManager.class);
 
-            String [] cameraIds = manager.getCameraIdListNoLazy();
+            String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
             if (cameraIds != null) {
                 for (String cameraId : cameraIds) {
-                    CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
+                    CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
                     mCharacteristicsHashMap.put(cameraId, chars);
                     Object thisClass = CameraCharacteristics.Key.class;
                     Class<CameraCharacteristics.Key<?>> keyClass =
@@ -1174,8 +1176,9 @@
         @Override
         public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
             mCameraId = cameraId;
-            mPreviewExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
-                    CameraExtensionsProxyService.this);
+            CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
+            mCameraManager.registerDeviceStateListener(chars);
+            mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
         }
 
         @Override
@@ -1200,13 +1203,16 @@
 
         @Override
         public void init(String cameraId, CameraMetadataNative chars) {
-            mPreviewExtender.init(cameraId, new CameraCharacteristics(chars));
+            CameraCharacteristics c = new CameraCharacteristics(chars);
+            mCameraManager.registerDeviceStateListener(c);
+            mPreviewExtender.init(cameraId, c);
         }
 
         @Override
         public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
-            return mPreviewExtender.isExtensionAvailable(cameraId,
-                    new CameraCharacteristics(chars));
+            CameraCharacteristics c = new CameraCharacteristics(chars);
+            mCameraManager.registerDeviceStateListener(c);
+            return mPreviewExtender.isExtensionAvailable(cameraId, c);
         }
 
         @Override
@@ -1283,8 +1289,9 @@
 
         @Override
         public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
-            mImageExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
-                    CameraExtensionsProxyService.this);
+            CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
+            mCameraManager.registerDeviceStateListener(chars);
+            mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
             mCameraId = cameraId;
         }
 
@@ -1310,13 +1317,16 @@
 
         @Override
         public void init(String cameraId, CameraMetadataNative chars) {
-            mImageExtender.init(cameraId, new CameraCharacteristics(chars));
+            CameraCharacteristics c = new CameraCharacteristics(chars);
+            mCameraManager.registerDeviceStateListener(c);
+            mImageExtender.init(cameraId, c);
         }
 
         @Override
         public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
-            return mImageExtender.isExtensionAvailable(cameraId,
-                    new CameraCharacteristics(chars));
+            CameraCharacteristics c = new CameraCharacteristics(chars);
+            mCameraManager.registerDeviceStateListener(c);
+            return mImageExtender.isExtensionAvailable(cameraId, c);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 19f8f38..7a4d11c 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -39,6 +39,7 @@
 import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
 import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
 import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.os.UserHandle.USER_NULL;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
 
@@ -195,7 +196,7 @@
     private EmergencyCallHelper mEmergencyCallHelper;
     private KeyguardManager mKeyguardManager;
 
-    private int mCurrentUser = -1;
+    private int mCurrentUser = USER_NULL;
 
     public SensorPrivacyService(Context context) {
         super(context);
@@ -228,9 +229,9 @@
 
     @Override
     public void onUserStarting(TargetUser user) {
-        if (mCurrentUser == -1) {
+        if (mCurrentUser == USER_NULL) {
             mCurrentUser = user.getUserIdentifier();
-            mSensorPrivacyServiceImpl.userSwitching(-1, user.getUserIdentifier());
+            mSensorPrivacyServiceImpl.userSwitching(USER_NULL, user.getUserIdentifier());
         }
     }
 
@@ -1294,13 +1295,13 @@
                 micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
                 camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
             }
-            if (prevMicState != micState) {
+            if (from == USER_NULL || prevMicState != micState) {
                 mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
                 setGlobalRestriction(MICROPHONE, micState);
             }
-            if (prevCamState != camState) {
+            if (from == USER_NULL || prevCamState != camState) {
                 mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
-                setGlobalRestriction(CAMERA, micState);
+                setGlobalRestriction(CAMERA, camState);
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2b30943..1997897 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5000,12 +5000,15 @@
                 // Since we kicked off all its pending restarts, there could be some open slots
                 // in the pending restarts list, schedule a check on it. We are posting to the same
                 // handler, so by the time of the check, those immediate restarts should be done.
-                mAm.mHandler.post(() ->
+                mAm.mHandler.post(() -> {
+                    final long now = SystemClock.uptimeMillis();
+                    synchronized (mAm) {
                         rescheduleServiceRestartIfPossibleLocked(
                                 getExtraRestartTimeInBetweenLocked(),
                                 mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN,
-                                "other", SystemClock.uptimeMillis())
-                );
+                                "other", now);
+                    }
+                });
             }
         }
         return didSomething;
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 3c6b682..baf0f39 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -673,11 +673,13 @@
 
             // Inform mStats about each applicable measured energy (unless addressed elsewhere).
             if (measuredEnergyDeltas != null) {
-                final long displayChargeUC = measuredEnergyDeltas.displayChargeUC;
-                if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) {
+                final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC;
+                if (displayChargeUC != null && displayChargeUC.length > 0) {
+                    // TODO (b/194107383): pass all display ordinals to mStats.
+                    final long primaryDisplayChargeUC = displayChargeUC[0];
                     // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
-                    mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState,
-                            elapsedRealtime);
+                    mStats.updateDisplayMeasuredEnergyStatsLocked(primaryDisplayChargeUC,
+                            screenState, elapsedRealtime);
                 }
 
                 final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC;
@@ -962,6 +964,7 @@
                 switch (consumer.type) {
                     case EnergyConsumerType.OTHER:
                     case EnergyConsumerType.CPU_CLUSTER:
+                    case EnergyConsumerType.DISPLAY:
                         break;
                     default:
                         Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index a9fca4f..0359aa5 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -49,6 +49,9 @@
     /** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */
     private final int mNumCpuClusterOrdinals;
 
+    /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */
+    private final int mNumDisplayOrdinals;
+
     /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
     private final int mNumOtherOrdinals;
 
@@ -95,6 +98,7 @@
 
         mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER,
                 idToConsumerMap);
+        mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap);
         mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap);
         mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
@@ -108,7 +112,7 @@
         public long[] cpuClusterChargeUC = null;
 
         /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
-        public long displayChargeUC = UNAVAILABLE;
+        public long[] displayChargeUC = null;
 
         /** The chargeUC for {@link EnergyConsumerType#GNSS}. */
         public long gnssChargeUC = UNAVAILABLE;
@@ -212,7 +216,10 @@
                     break;
 
                 case EnergyConsumerType.DISPLAY:
-                    output.displayChargeUC = deltaChargeUC;
+                    if (output.displayChargeUC == null) {
+                        output.displayChargeUC = new long[mNumDisplayOrdinals];
+                    }
+                    output.displayChargeUC[ordinal]  = deltaChargeUC;
                     break;
 
                 case EnergyConsumerType.GNSS:
diff --git a/services/core/java/com/android/server/criticalevents/OWNERS b/services/core/java/com/android/server/criticalevents/OWNERS
new file mode 100644
index 0000000..9c3136c
--- /dev/null
+++ b/services/core/java/com/android/server/criticalevents/OWNERS
@@ -0,0 +1,2 @@
+benmiles@google.com
+gaillard@google.com
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bf5208a..63d32c8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -125,6 +125,7 @@
     private static final int MSG_IGNORE_PROXIMITY = 8;
     private static final int MSG_STOP = 9;
     private static final int MSG_UPDATE_BRIGHTNESS = 10;
+    private static final int MSG_UPDATE_RBC = 11;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -422,13 +423,13 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
     private float mTemporaryAutoBrightnessAdjustment;
 
-    // Whether a reduce bright colors (rbc) change has been initiated by the user. We want to
-    // retain the current backlight level when rbc is toggled, since rbc additionally makes the
-    // screen appear dimmer using screen colors rather than backlight levels, and therefore we
-    // don't actually want to compensate for this by then in/decreasing the backlight when
-    // toggling this feature.
+    // Whether reduce bright colors (rbc) has been turned on, or a change in strength has been
+    // requested. We want to retain the current backlight level when rbc is toggled, since rbc
+    // additionally makes the screen appear dimmer using screen colors rather than backlight levels,
+    // and therefore we don't actually want to compensate for this by then in/decreasing the
+    // backlight when toggling this feature.
     // This should be false during system start up.
-    private boolean mPendingUserRbcChange;
+    private boolean mPendingRbcOnOrChanged = false;
 
     // Animators.
     private ObjectAnimator mColorFadeOnAnimator;
@@ -564,23 +565,35 @@
                 @Override
                 public void onReduceBrightColorsActivationChanged(boolean activated,
                         boolean userInitiated) {
-                    applyReduceBrightColorsSplineAdjustment(userInitiated);
+                    applyReduceBrightColorsSplineAdjustment(
+                            /* rbcStrengthChanged= */ false, activated);
+
                 }
 
                 @Override
                 public void onReduceBrightColorsStrengthChanged(int strength) {
-                    applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+                    applyReduceBrightColorsSplineAdjustment(
+                            /* rbcStrengthChanged= */ true, /* justActivated= */ false);
                 }
             });
             if (active) {
-                applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+                applyReduceBrightColorsSplineAdjustment(
+                        /* rbcStrengthChanged= */ false,  /* justActivated= */ false);
             }
         } else {
             mCdsi = null;
         }
     }
 
-    private void applyReduceBrightColorsSplineAdjustment(boolean userInitiated) {
+    private void applyReduceBrightColorsSplineAdjustment(
+            boolean rbcStrengthChanged, boolean justActivated) {
+        final int strengthChanged = rbcStrengthChanged ? 1 : 0;
+        final int activated = justActivated ? 1 : 0;
+        mHandler.obtainMessage(MSG_UPDATE_RBC, strengthChanged, activated).sendToTarget();
+        sendUpdatePowerState();
+    }
+
+    private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
         if (mBrightnessMapper == null) {
             Log.w(TAG, "No brightness mapping available to recalculate splines");
             return;
@@ -591,8 +604,13 @@
             adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
         }
         mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
-        mPendingUserRbcChange = userInitiated;
-        sendUpdatePowerState();
+
+        mPendingRbcOnOrChanged = strengthChanged || justActivated;
+
+        // Reset model if strength changed OR rbc is turned off
+        if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.resetShortTermModel();
+        }
     }
 
     /**
@@ -926,7 +944,8 @@
 
     private void reloadReduceBrightColours() {
         if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
-            applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+            applyReduceBrightColorsSplineAdjustment(
+                    /* rbcStrengthChanged= */ false, /* justActivated= */ false);
         }
     }
 
@@ -2072,21 +2091,24 @@
         return true;
     }
 
+    // We want to return true if the user has set the screen brightness.
+    // If they have just turned RBC on (and therefore added that interaction to the curve),
+    // or changed the brightness another way, then we should return true.
     private boolean updateUserSetScreenBrightness() {
-        final boolean brightnessSplineChanged = mPendingUserRbcChange;
-        if (mPendingUserRbcChange && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
+        final boolean treatAsIfUserChanged = mPendingRbcOnOrChanged;
+        if (treatAsIfUserChanged && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
             mLastUserSetScreenBrightness = mCurrentScreenBrightnessSetting;
         }
-        mPendingUserRbcChange = false;
+        mPendingRbcOnOrChanged = false;
 
         if ((Float.isNaN(mPendingScreenBrightnessSetting)
                 || mPendingScreenBrightnessSetting < 0.0f)) {
-            return brightnessSplineChanged;
+            return treatAsIfUserChanged;
         }
         if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
             mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            return brightnessSplineChanged;
+            return treatAsIfUserChanged;
         }
         setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
         mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
@@ -2428,6 +2450,12 @@
                     }
                     handleSettingsChange(false /*userSwitch*/);
                     break;
+
+                case MSG_UPDATE_RBC:
+                    final int strengthChanged = msg.arg1;
+                    final int justActivated = msg.arg2;
+                    handleRbcChanged(strengthChanged == 1, justActivated == 1);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 9be9505..15d2a05 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -73,7 +73,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.vibrator.StepSegment;
@@ -175,9 +174,6 @@
     /** TODO(b/169067926): Remove this. */
     private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
 
-    public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
-            SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
-
     // Pointer to native input manager service object.
     private final long mPtr;
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index cfc32ce..ec8e5e4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2355,6 +2355,8 @@
 
         if (displayIdToShowIme == INVALID_DISPLAY) {
             mImeHiddenByDisplayPolicy = true;
+            hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
             return InputBindResult.NO_IME;
         }
         mImeHiddenByDisplayPolicy = false;
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
new file mode 100644
index 0000000..0045499
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -0,0 +1,246 @@
+/*
+ * 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.locales;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.ILocaleManager;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * The implementation of ILocaleManager.aidl.
+ *
+ * <p>This service is API entry point for storing app-specific UI locales
+ */
+public class LocaleManagerService extends SystemService {
+    private static final String TAG = "LocaleManagerService";
+    private final Context mContext;
+    private final LocaleManagerService.LocaleManagerBinderService mBinderService;
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private PackageManagerInternal mPackageManagerInternal;
+    public static final boolean DEBUG = false;
+
+    public LocaleManagerService(Context context) {
+        super(context);
+        mContext = context;
+        mBinderService = new LocaleManagerBinderService();
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+    }
+
+    @VisibleForTesting
+    LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
+            ActivityManagerInternal activityManagerInternal,
+            PackageManagerInternal packageManagerInternal) {
+        super(context);
+        mContext = context;
+        mBinderService = new LocaleManagerBinderService();
+        mActivityTaskManagerInternal = activityTaskManagerInternal;
+        mActivityManagerInternal = activityManagerInternal;
+        mPackageManagerInternal = packageManagerInternal;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.LOCALE_SERVICE, mBinderService);
+    }
+
+    private final class LocaleManagerBinderService extends ILocaleManager.Stub {
+        @Override
+        public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
+                @NonNull LocaleList locales) throws RemoteException {
+            LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales);
+        }
+
+        @Override
+        @NonNull
+        public LocaleList getApplicationLocales(@NonNull String appPackageName,
+                @UserIdInt int userId) throws RemoteException {
+            return LocaleManagerService.this.getApplicationLocales(appPackageName, userId);
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            LocaleManagerService.this.dump(fd, pw, args);
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                FileDescriptor err, String[] args, ShellCallback callback,
+                ResultReceiver resultReceiver) {
+            (new LocaleManagerShellCommand(mBinderService))
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+    }
+
+    /**
+     * Sets the current UI locales for a specified app.
+     */
+    public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
+            @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException {
+        requireNonNull(appPackageName);
+        requireNonNull(locales);
+
+        //Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
+        userId = mActivityManagerInternal.handleIncomingUser(
+                Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+                "setApplicationLocales", appPackageName);
+
+        // This function handles two types of set operations:
+        // 1.) A normal, non-privileged app setting its own locale.
+        // 2.) A privileged system service setting locales of another package.
+        // The least privileged case is a normal app performing a set, so check that first and
+        // set locales if the package name is owned by the app. Next, check if the caller has the
+        // necessary permission and set locales.
+        boolean isCallerOwner = isPackageOwnedByCaller(appPackageName, userId);
+        if (!isCallerOwner) {
+            enforceChangeConfigurationPermission();
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            setApplicationLocalesUnchecked(appPackageName, userId, locales);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+
+    private void setApplicationLocalesUnchecked(@NonNull String appPackageName,
+            @UserIdInt int userId, @NonNull LocaleList locales) {
+        if (DEBUG) {
+            Slog.d(TAG, "setApplicationLocales: setting locales for package " + appPackageName
+                    + " and user " + userId);
+        }
+        final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+                mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
+                        userId);
+        updater.setLocales(locales).commit();
+    }
+
+
+    /**
+     * Checks if the package is owned by the calling app or not for the given user id.
+     *
+     * @throws IllegalArgumentException if package not found for given userid
+     */
+    private boolean isPackageOwnedByCaller(String appPackageName, int userId) {
+        final int uid = mPackageManagerInternal
+                .getPackageUid(appPackageName, /* flags */ 0, userId);
+        if (uid < 0) {
+            Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId);
+            throw new IllegalArgumentException("Unknown package: " + appPackageName
+                    + " for user " + userId);
+        }
+        //Once valid package found, ignore the userId part for validating package ownership
+        //as apps with INTERACT_ACROSS_USERS permission could be changing locale for different user.
+        return UserHandle.isSameApp(Binder.getCallingUid(), uid);
+    }
+
+    private void enforceChangeConfigurationPermission() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.CHANGE_CONFIGURATION, "setApplicationLocales");
+    }
+
+    /**
+     * Returns the current UI locales for the specified app.
+     */
+    @NonNull
+    public LocaleList getApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId)
+            throws RemoteException, IllegalArgumentException {
+        requireNonNull(appPackageName);
+
+        //Allow apps with INTERACT_ACROSS_USERS permission to query locales for different user.
+        userId = mActivityManagerInternal.handleIncomingUser(
+                Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
+                "getApplicationLocales", appPackageName);
+
+        // This function handles two types of query operations:
+        // 1.) A normal, non-privileged app querying its own locale.
+        // 2.) A privileged system service querying locales of another package.
+        // The least privileged case is a normal app performing a query, so check that first and
+        // get locales if the package name is owned by the app. Next, check if the caller has the
+        // necessary permission and get locales.
+        if (!isPackageOwnedByCaller(appPackageName, userId)) {
+            enforceReadAppSpecificLocalesPermission();
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return getApplicationLocalesUnchecked(appPackageName, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private LocaleList getApplicationLocalesUnchecked(@NonNull String appPackageName,
+            @UserIdInt int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "getApplicationLocales: fetching locales for package " + appPackageName
+                    + " and user " + userId);
+        }
+
+        final ActivityTaskManagerInternal.PackageConfig appConfig =
+                mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId);
+        if (appConfig == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "getApplicationLocales: application config not found for "
+                        + appPackageName + " and user id " + userId);
+            }
+            return LocaleList.getEmptyLocaleList();
+        }
+        LocaleList locales = appConfig.mLocales;
+        return locales != null ? locales : LocaleList.getEmptyLocaleList();
+    }
+
+    private void enforceReadAppSpecificLocalesPermission() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.READ_APP_SPECIFIC_LOCALES,
+                "getApplicationLocales");
+    }
+
+    /**
+     * Dumps useful info related to service.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        // TODO(b/201766221): Implement when there is state.
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
new file mode 100644
index 0000000..769ea17
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -0,0 +1,159 @@
+/*
+ * 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.locales;
+
+import android.app.ActivityManager;
+import android.app.ILocaleManager;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell commands for {@link LocaleManagerService}
+ */
+public class LocaleManagerShellCommand extends ShellCommand {
+    private final ILocaleManager mBinderService;
+
+    LocaleManagerShellCommand(ILocaleManager localeManager) {
+        mBinderService = localeManager;
+    }
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "set-app-locales":
+                return runSetAppLocales();
+            case "get-app-locales":
+                return runGetAppLocales();
+            default: {
+                return handleDefaultCommands(cmd);
+            }
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Locale manager (locale) shell commands:");
+        pw.println("  help");
+        pw.println("      Print this help text.");
+        pw.println("  set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]");
+        pw.println("      Set the locales for the specified app.");
+        pw.println("      --user <USER_ID>: apply for the given user, "
+                + "the current user is used when unspecified.");
+        pw.println("      --locales <LOCALE_INFO>: The language tags of locale to be included "
+                + "as a single String separated by commas");
+        pw.println("                 Empty locale list is used when unspecified.");
+        pw.println("                 eg. en,en-US,hi ");
+        pw.println("  get-app-locales <PACKAGE_NAME> [--user <USER_ID>]");
+        pw.println("      Get the locales for the specified app.");
+        pw.println("      --user <USER_ID>: get for the given user, "
+                + "the current user is used when unspecified.");
+    }
+
+    private int runSetAppLocales() {
+        final PrintWriter err = getErrPrintWriter();
+        String packageName = getNextArg();
+
+        if (packageName != null) {
+            int userId = ActivityManager.getCurrentUser();
+            LocaleList locales = LocaleList.getEmptyLocaleList();
+            do {
+                String option = getNextOption();
+                if (option == null) {
+                    break;
+                }
+                switch (option) {
+                    case "--user": {
+                        userId = UserHandle.parseUserArg(getNextArgRequired());
+                        break;
+                    }
+                    case "--locales": {
+                        locales = parseLocales();
+                        break;
+                    }
+                    default: {
+                        throw new IllegalArgumentException("Unknown option: " + option);
+                    }
+                }
+            } while (true);
+
+            try {
+                mBinderService.setApplicationLocales(packageName, userId, locales);
+            } catch (RemoteException e) {
+                getOutPrintWriter().println("Remote Exception: " + e);
+            } catch (IllegalArgumentException e) {
+                getOutPrintWriter().println("Unknown package " + packageName
+                        + " for userId " + userId);
+            }
+        } else {
+            err.println("Error: no package specified");
+            return -1;
+        }
+        return 0;
+    }
+
+    private int runGetAppLocales() {
+        final PrintWriter err = getErrPrintWriter();
+        String packageName = getNextArg();
+
+        if (packageName != null) {
+            int userId = ActivityManager.getCurrentUser();
+            do {
+                String option = getNextOption();
+                if (option == null) {
+                    break;
+                }
+                if ("--user".equals(option)) {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                } else {
+                    throw new IllegalArgumentException("Unknown option: " + option);
+                }
+            } while (true);
+            try {
+                LocaleList locales = mBinderService.getApplicationLocales(packageName, userId);
+                getOutPrintWriter().println("Locales for " + packageName
+                        + " for user " + userId + " are " + locales);
+            } catch (RemoteException e) {
+                getOutPrintWriter().println("Remote Exception: " + e);
+            } catch (IllegalArgumentException e) {
+                getOutPrintWriter().println("Unknown package " + packageName
+                        + " for userId " + userId);
+            }
+        } else {
+            err.println("Error: no package specified");
+            return -1;
+        }
+        return 0;
+    }
+
+    private LocaleList parseLocales() {
+        if (getRemainingArgsCount() <= 0) {
+            return LocaleList.getEmptyLocaleList();
+        }
+        String[] args = peekRemainingArgs();
+        String inputLocales = args[0];
+        LocaleList locales = LocaleList.forLanguageTags(inputLocales);
+        return locales;
+    }
+}
diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS
new file mode 100644
index 0000000..be284a7
--- /dev/null
+++ b/services/core/java/com/android/server/locales/OWNERS
@@ -0,0 +1,3 @@
+roosa@google.com
+pratyushmore@google.com
+goldmanj@google.com
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 442abc9..b676f28 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -207,6 +207,12 @@
      */
     private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false);
 
+    /**
+     * True if a permissions query has been issued and is being processed. Used to prevent too many
+     * queries from being issued by a single client at once.
+     */
+    private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false);
+
     /*
      * Map containing all nanoapps this client has a messaging channel with and whether it is
      * allowed to communicate over that channel. A channel is defined to have been opened if the
@@ -233,11 +239,11 @@
     private final IContextHubTransactionCallback mQueryPermsCallback =
             new IContextHubTransactionCallback.Stub() {
             @Override
-            public void onTransactionComplete(int result) {
-            }
+            public void onTransactionComplete(int result) {}
 
             @Override
             public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                mIsPermQueryIssued.set(false);
                 if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
                     Log.e(TAG, "Permissions query failed, but still received nanoapp state");
                 } else if (nanoAppStateList != null) {
@@ -650,9 +656,11 @@
      * communicated with in the past.
      */
     private void checkNanoappPermsAsync() {
-        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
-                mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
-        mTransactionManager.addTransaction(transaction);
+        if (!mIsPermQueryIssued.getAndSet(true)) {
+            ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                    mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+            mTransactionManager.addTransaction(transaction);
+        }
     }
 
     private int updateNanoAppAuthState(
diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
index a47c48f..2df2101 100644
--- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
@@ -100,7 +100,7 @@
             return false;
         }
 
-        return mAppOps.checkOpNoThrow(permissionLevel, identity);
+        return mAppOps.checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
     }
 
     protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity);
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index ad87c45..22a675a 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -105,15 +105,20 @@
 
         synchronized (mLock) {
             mDeviceIdleHelper.addListener(this);
-            onDeviceIdleChanged(mDeviceIdleHelper.isDeviceIdle());
+            mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
+            mDeviceStationaryHelper.addListener(this);
+            mDeviceStationary = false;
+            mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+
+            onThrottlingChangedLocked(false);
         }
     }
 
     @Override
     protected void onStop() {
         synchronized (mLock) {
+            mDeviceStationaryHelper.removeListener(this);
             mDeviceIdleHelper.removeListener(this);
-            onDeviceIdleChanged(false);
 
             mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
             mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
@@ -146,26 +151,13 @@
             }
 
             mDeviceIdle = deviceIdle;
-
-            if (deviceIdle) {
-                // device stationary helper will deliver an immediate listener update
-                mDeviceStationaryHelper.addListener(this);
-            } else {
-                mDeviceStationaryHelper.removeListener(this);
-                mDeviceStationary = false;
-                mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
-            }
+            onThrottlingChangedLocked(false);
         }
     }
 
     @Override
     public void onDeviceStationaryChanged(boolean deviceStationary) {
         synchronized (mLock) {
-            if (!mDeviceIdle) {
-                // stationary detection is only registered while idle - ignore late notifications
-                return;
-            }
-
             if (mDeviceStationary == deviceStationary) {
                 return;
             }
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index b1676d0..ea554d3 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -30,6 +30,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.permission.ILegacyPermissionManager;
+import android.util.EventLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -187,10 +188,25 @@
     private void verifyCallerCanCheckAccess(String packageName, String message, int pid, int uid) {
         // If the check is being requested by an app then only allow the app to query its own
         // access status.
+        boolean reportError = false;
         int callingUid = mInjector.getCallingUid();
         int callingPid = mInjector.getCallingPid();
         if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID && (callingUid != uid
                 || callingPid != pid)) {
+            reportError = true;
+        }
+        // If the query is against an app on the device, then the check should only be allowed if
+        // the provided uid matches that of the specified package.
+        if (packageName != null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+            int packageUid = mInjector.getPackageUidForUser(packageName, UserHandle.getUserId(uid));
+            if (uid != packageUid) {
+                EventLog.writeEvent(0x534e4554, "193441322",
+                        UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID
+                                ? callingUid : uid, "Package uid mismatch");
+                reportError = true;
+            }
+        }
+        if (reportError) {
             String response = String.format(
                     "Calling uid %d, pid %d cannot access for package %s (uid=%d, pid=%d): %s",
                     callingUid, callingPid, packageName, uid, pid, message);
@@ -385,12 +401,14 @@
     @VisibleForTesting
     public static class Injector {
         private final Context mContext;
+        private final PackageManagerInternal mPackageManagerInternal;
 
         /**
          * Public constructor that accepts a {@code context} within which to operate.
          */
         public Injector(@NonNull Context context) {
             mContext = context;
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
         /**
@@ -453,5 +471,12 @@
             return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0,
                     UserHandle.getUserHandleForUid(uid));
         }
+
+        /**
+         * Returns the uid for the specified {@code packageName} under the provided {@code userId}.
+         */
+        public int getPackageUidForUser(String packageName, int userId) {
+            return mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 051f555..c356fec 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -195,6 +195,12 @@
 
     @Override // Binder interface
     public void doKeyguardTimeout(Bundle options) {
+        int userId = mKeyguardStateMonitor.getCurrentUser();
+        if (mKeyguardStateMonitor.isSecure(userId)) {
+            // Preemptively inform the cache that the keyguard will soon be showing, as calls to
+            // doKeyguardTimeout are a signal to lock the device as soon as possible.
+            mKeyguardStateMonitor.onShowingStateChanged(true, userId);
+        }
         try {
             mService.doKeyguardTimeout(options);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index add0b01..f0f62ed 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -83,8 +83,14 @@
         return mHasLockscreenWallpaper;
     }
 
+    public int getCurrentUser() {
+        return mCurrentUserId;
+    }
+
     @Override // Binder interface
-    public void onShowingStateChanged(boolean showing) {
+    public void onShowingStateChanged(boolean showing, int userId) {
+        if (userId != mCurrentUserId) return;
+
         mIsShowing = showing;
 
         mCallback.onShowingChanged();
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e7b2756..ca24ed9 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -43,7 +43,6 @@
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -86,6 +85,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -1284,14 +1284,10 @@
     }
 
     private SparseIntArray getExtensionVersions() {
-        // This list must be updated whenever the current API level is increased, or should be
-        // replaced when we have another way of determining the relevant SDK versions.
-        final int[] relevantSdkVersions = { Build.VERSION_CODES.R, Build.VERSION_CODES.S };
-
-        SparseIntArray result = new SparseIntArray(relevantSdkVersions.length);
-        for (int i = 0; i < relevantSdkVersions.length; i++) {
-            result.put(relevantSdkVersions[i],
-                    SdkExtensions.getExtensionVersion(relevantSdkVersions[i]));
+        Map<Integer, Integer> allExtensionVersions = SdkExtensions.getAllExtensionVersions();
+        SparseIntArray result = new SparseIntArray(allExtensionVersions.size());
+        for (int extension : allExtensionVersions.keySet()) {
+            result.put(extension, allExtensionVersions.get(extension));
         }
         return result;
     }
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
new file mode 100644
index 0000000..3f4def6
--- /dev/null
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -0,0 +1,366 @@
+/*
+ * 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.utils;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.PriorityQueue;
+import java.util.function.Predicate;
+
+/**
+ * An {@link AlarmManager.OnAlarmListener} that will queue up all pending alarms and only
+ * schedule one alarm for the earliest alarm. Since {@link AlarmManager} has a maximum limit on the
+ * number of alarms that can be set at one time, this allows clients to maintain alarm times for
+ * various keys without risking hitting the AlarmManager alarm limit. Only one alarm time will be
+ * kept for each key {@code K}.
+ *
+ * @param <K> Any class that will be used as the key. Must have a proper equals() implementation.
+ * @hide
+ */
+public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener {
+    private static final String TAG = AlarmQueue.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final long NOT_SCHEDULED = -1;
+
+    /**
+     * Internal priority queue for each key's alarm, ordered by the time the alarm should go off.
+     * The pair is the key and its associated alarm time (in the elapsed realtime timebase).
+     */
+    private static class AlarmPriorityQueue<Q> extends PriorityQueue<Pair<Q, Long>> {
+        AlarmPriorityQueue() {
+            super(1, (o1, o2) -> (int) (o1.second - o2.second));
+        }
+
+        /**
+         * Remove any instances of the key from the queue.
+         *
+         * @return true if an instance was removed, false otherwise.
+         */
+        public boolean removeKey(@NonNull Q key) {
+            boolean removed = false;
+            Pair[] alarms = toArray(new Pair[size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                if (key.equals(alarms[i].first)) {
+                    remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            return removed;
+        }
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+
+    /** Runnable used to schedule an alarm with AlarmManager. NEVER run this with the lock held. */
+    private final Runnable mScheduleAlarmRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mHandler.removeCallbacks(this);
+
+            final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+            if (alarmManager == null) {
+                // The system isn't fully booted. Clients of this class may not have
+                // direct access to (be notified when) the system is ready, so retry
+                // setting the alarm after some delay. Leave enough time so that we don't cause
+                // any unneeded startup delay.
+                mHandler.postDelayed(this, 30_000);
+                return;
+            }
+            final long nextTriggerTimeElapsed;
+            final long minTimeBetweenAlarmsMs;
+            synchronized (mLock) {
+                if (mTriggerTimeElapsed == NOT_SCHEDULED) {
+                    return;
+                }
+                nextTriggerTimeElapsed = mTriggerTimeElapsed;
+                minTimeBetweenAlarmsMs = mMinTimeBetweenAlarmsMs;
+            }
+            // Never call out to AlarmManager with the lock held. This could sit below AM.
+            if (mExactAlarm) {
+                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME,
+                        nextTriggerTimeElapsed, mAlarmTag, AlarmQueue.this, mHandler);
+            } else {
+                alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME,
+                        nextTriggerTimeElapsed, minTimeBetweenAlarmsMs / 2,
+                        mAlarmTag, AlarmQueue.this, mHandler);
+            }
+        }
+    };
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Injector mInjector;
+
+    @GuardedBy("mLock")
+    private final AlarmPriorityQueue<K> mAlarmPriorityQueue = new AlarmPriorityQueue<>();
+    private final String mAlarmTag;
+    private final String mDumpTitle;
+    /** Whether to use an exact alarm or an inexact alarm. */
+    private final boolean mExactAlarm;
+    /** The minimum amount of time between check alarms. */
+    @GuardedBy("mLock")
+    private long mMinTimeBetweenAlarmsMs;
+    /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
+    @GuardedBy("mLock")
+    @ElapsedRealtimeLong
+    private long mTriggerTimeElapsed = NOT_SCHEDULED;
+
+    /**
+     * @param alarmTag               The tag to use when scheduling the alarm with AlarmManager.
+     * @param dumpTitle              The title to use when dumping state.
+     * @param exactAlarm             Whether or not to use an exact alarm. If false, this will use
+     *                               an inexact window alarm.
+     * @param minTimeBetweenAlarmsMs The minimum amount of time that should be between alarms. If
+     *                               one alarm will go off too soon after another, the second one
+     *                               will be delayed to meet this minimum time.
+     */
+    public AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag,
+            @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs) {
+        this(context, looper, alarmTag, dumpTitle, exactAlarm, minTimeBetweenAlarmsMs,
+                new Injector());
+    }
+
+    @VisibleForTesting
+    AlarmQueue(@NonNull Context context, @NonNull Looper looper, @NonNull String alarmTag,
+            @NonNull String dumpTitle, boolean exactAlarm, long minTimeBetweenAlarmsMs,
+            @NonNull Injector injector) {
+        mContext = context;
+        mAlarmTag = alarmTag;
+        mDumpTitle = dumpTitle.trim();
+        mExactAlarm = exactAlarm;
+        mHandler = new Handler(looper);
+        mInjector = injector;
+        if (minTimeBetweenAlarmsMs < 0) {
+            throw new IllegalArgumentException("min time between alarms must be non-negative");
+        }
+        mMinTimeBetweenAlarmsMs = minTimeBetweenAlarmsMs;
+    }
+
+    /**
+     * Add an alarm for the specified key that should go off at the provided time
+     * (in the elapsed realtime timebase). This will also remove any existing alarm for the key.
+     */
+    public void addAlarm(K key, @ElapsedRealtimeLong long alarmTimeElapsed) {
+        synchronized (mLock) {
+            final boolean removed = mAlarmPriorityQueue.removeKey(key);
+            mAlarmPriorityQueue.offer(new Pair<>(key, alarmTimeElapsed));
+            if (mTriggerTimeElapsed == NOT_SCHEDULED || removed
+                    || alarmTimeElapsed < mTriggerTimeElapsed) {
+                setNextAlarmLocked();
+            }
+        }
+    }
+
+    /**
+     * Get the current minimum time between alarms.
+     *
+     * @see #setMinTimeBetweenAlarmsMs(long)
+     */
+    public long getMinTimeBetweenAlarmsMs() {
+        synchronized (mLock) {
+            return mMinTimeBetweenAlarmsMs;
+        }
+    }
+
+    /** Remove the alarm for this specific key. */
+    public void removeAlarmForKey(K key) {
+        synchronized (mLock) {
+            if (mAlarmPriorityQueue.removeKey(key)) {
+                setNextAlarmLocked();
+            }
+        }
+    }
+
+    /** Remove all alarms tied to the specified user. */
+    public void removeAlarmsForUserId(@UserIdInt int userId) {
+        boolean removed = false;
+        synchronized (mLock) {
+            Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                final K key = (K) alarms[i].first;
+                if (isForUser(key, userId)) {
+                    mAlarmPriorityQueue.remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            if (removed) {
+                setNextAlarmLocked();
+            }
+        }
+    }
+
+    /** Cancel and remove all alarms. */
+    public void removeAllAlarms() {
+        synchronized (mLock) {
+            mAlarmPriorityQueue.clear();
+            setNextAlarmLocked(0);
+        }
+    }
+
+    /** Remove all alarms that satisfy the predicate. */
+    protected void removeAlarmsIf(@NonNull Predicate<K> predicate) {
+        boolean removed = false;
+        synchronized (mLock) {
+            Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                final K key = (K) alarms[i].first;
+                if (predicate.test(key)) {
+                    mAlarmPriorityQueue.remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            if (removed) {
+                setNextAlarmLocked();
+            }
+        }
+    }
+
+    /**
+     * Update the minimum time that should be between alarms. This helps avoid thrashing when alarms
+     * are scheduled very closely together and may result in some batching of expired alarms.
+     */
+    public void setMinTimeBetweenAlarmsMs(long minTimeMs) {
+        if (minTimeMs < 0) {
+            throw new IllegalArgumentException("min time between alarms must be non-negative");
+        }
+        synchronized (mLock) {
+            mMinTimeBetweenAlarmsMs = minTimeMs;
+        }
+    }
+
+    /** Return true if the key is for the specified user. */
+    protected abstract boolean isForUser(@NonNull K key, int userId);
+
+    /** Handle all of the alarms that have now expired (their trigger time has passed). */
+    protected abstract void processExpiredAlarms(@NonNull ArraySet<K> expired);
+
+    /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
+    @GuardedBy("mLock")
+    private void setNextAlarmLocked() {
+        setNextAlarmLocked(mInjector.getElapsedRealtime());
+    }
+
+    /**
+     * Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue, using
+     * {@code earliestTriggerElapsed} as a floor.
+     */
+    @GuardedBy("mLock")
+    private void setNextAlarmLocked(long earliestTriggerElapsed) {
+        if (mAlarmPriorityQueue.size() == 0) {
+            mHandler.post(() -> {
+                // Never call out to AlarmManager with the lock held. This could sit below AM.
+                final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+                if (alarmManager != null) {
+                    // This should only be null at boot time. No concerns around not
+                    // cancelling if we get null here, so no need to retry.
+                    alarmManager.cancel(this);
+                }
+            });
+            mTriggerTimeElapsed = NOT_SCHEDULED;
+            return;
+        }
+
+        final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
+        final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second);
+        // Only schedule the alarm if one of the following is true:
+        // 1. There isn't one currently scheduled
+        // 2. The new alarm is significantly earlier than the previous alarm. If it's
+        // earlier but not significantly so, then we essentially delay the check for some
+        // apps by up to a minute.
+        // 3. The alarm is after the current alarm.
+        if (mTriggerTimeElapsed == NOT_SCHEDULED
+                || nextTriggerTimeElapsed < mTriggerTimeElapsed - MINUTE_IN_MILLIS
+                || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
+            if (DEBUG) {
+                Slog.d(TAG, "Scheduling alarm at " + nextTriggerTimeElapsed
+                        + " for key " + alarm.first);
+            }
+            mTriggerTimeElapsed = nextTriggerTimeElapsed;
+            mHandler.post(mScheduleAlarmRunnable);
+        }
+    }
+
+    @Override
+    public void onAlarm() {
+        final ArraySet<K> expired = new ArraySet<>();
+        synchronized (mLock) {
+            final long nowElapsed = mInjector.getElapsedRealtime();
+            while (mAlarmPriorityQueue.size() > 0) {
+                final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
+                if (alarm.second <= nowElapsed) {
+                    expired.add(alarm.first);
+                    mAlarmPriorityQueue.remove(alarm);
+                } else {
+                    break;
+                }
+            }
+            setNextAlarmLocked(nowElapsed + mMinTimeBetweenAlarmsMs);
+        }
+        // Don't "call out" with the lock held to avoid potential deadlocks.
+        if (expired.size() > 0) {
+            processExpiredAlarms(expired);
+        }
+    }
+
+    /** Dump internal state. */
+    public void dump(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.print(mDumpTitle);
+            pw.println(" alarms:");
+            pw.increaseIndent();
+
+            if (mAlarmPriorityQueue.size() == 0) {
+                pw.println("NOT WAITING");
+            } else {
+                Pair[] alarms = mAlarmPriorityQueue.toArray(new Pair[mAlarmPriorityQueue.size()]);
+                for (int i = 0; i < alarms.length; ++i) {
+                    final K key = (K) alarms[i].first;
+                    pw.print(key);
+                    pw.print(": ");
+                    pw.print(alarms[i].second);
+                    pw.println();
+                }
+            }
+
+            pw.decreaseIndent();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index 802ab5b..a0363ef 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -29,11 +29,11 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArrayMap;
 import android.util.proto.ProtoOutputStream;
@@ -45,8 +45,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
-
-import java.util.PriorityQueue;
+import com.android.server.utils.AlarmQueue;
 
 /**
  * Base class for trackers that track whether an app has exceeded a count quota.
@@ -88,10 +87,10 @@
     private final ArraySet<QuotaChangeListener> mQuotaChangeListeners = new ArraySet<>();
 
     /**
-     * Listener to track and manage when each package comes back within quota.
+     * Alarm queue to track and manage when each package comes back within quota.
      */
     @GuardedBy("mLock")
-    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
+    private final InQuotaAlarmQueue mInQuotaAlarmQueue;
 
     /** "Free quota status" for apps. */
     @GuardedBy("mLock")
@@ -163,6 +162,8 @@
         mContext = context;
         mInjector = injector;
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        // The operation should be fast enough to put it on the FgThread.
+        mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, FgThread.getHandler().getLooper());
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
@@ -179,7 +180,7 @@
     /** Remove all saved events from the tracker. */
     public void clear() {
         synchronized (mLock) {
-            mInQuotaAlarmListener.clearLocked();
+            mInQuotaAlarmQueue.removeAllAlarms();
             mFreeQuota.clear();
 
             dropEverythingLocked();
@@ -367,7 +368,7 @@
             return;
         }
 
-        mInQuotaAlarmListener.removeAlarmsLocked(userId, packageName);
+        mInQuotaAlarmQueue.removeAlarms(userId, packageName);
 
         mFreeQuota.delete(userId, packageName);
 
@@ -379,7 +380,7 @@
 
     @GuardedBy("mLock")
     private void onUserRemovedLocked(int userId) {
-        mInQuotaAlarmListener.removeAlarmsLocked(userId);
+        mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
         mFreeQuota.delete(userId);
 
         handleRemovedUserLocked(userId);
@@ -434,190 +435,43 @@
                 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
                         + " even though it's within quota");
             }
-            mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
+            mInQuotaAlarmQueue.removeAlarmForKey(new Uptc(userId, packageName, tag));
             maybeUpdateQuotaStatus(userId, packageName, tag);
             return;
         }
 
-        mInQuotaAlarmListener.addAlarmLocked(new Uptc(userId, packageName, tag),
+        mInQuotaAlarmQueue.addAlarm(new Uptc(userId, packageName, tag),
                 getInQuotaTimeElapsedLocked(userId, packageName, tag));
     }
 
     @GuardedBy("mLock")
     void cancelScheduledStartAlarmLocked(final int userId,
             @NonNull final String packageName, @Nullable final String tag) {
-        mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
-    }
-
-    static class AlarmQueue extends PriorityQueue<Pair<Uptc, Long>> {
-        AlarmQueue() {
-            super(1, (o1, o2) -> (int) (o1.second - o2.second));
-        }
-
-        /**
-         * Remove any instances of the Uptc from the queue.
-         *
-         * @return true if an instance was removed, false otherwise.
-         */
-        boolean remove(@NonNull Uptc uptc) {
-            boolean removed = false;
-            Pair[] alarms = toArray(new Pair[size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                if (uptc.equals(alarms[i].first)) {
-                    remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            return removed;
-        }
+        mInQuotaAlarmQueue.removeAlarmForKey(new Uptc(userId, packageName, tag));
     }
 
     /** Track when UPTCs are expected to come back into quota. */
-    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
-        @GuardedBy("mLock")
-        private final AlarmQueue mAlarmQueue = new AlarmQueue();
-        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
-        @GuardedBy("mLock")
-        private long mTriggerTimeElapsed = 0;
-
-        @GuardedBy("mLock")
-        void addAlarmLocked(@NonNull Uptc uptc, long inQuotaTimeElapsed) {
-            mAlarmQueue.remove(uptc);
-            mAlarmQueue.offer(new Pair<>(uptc, inQuotaTimeElapsed));
-            setNextAlarmLocked();
-        }
-
-        @GuardedBy("mLock")
-        void clearLocked() {
-            cancelAlarm(this);
-            mAlarmQueue.clear();
-            mTriggerTimeElapsed = 0;
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmLocked(@NonNull Uptc uptc) {
-            if (mAlarmQueue.remove(uptc)) {
-                if (mAlarmQueue.size() == 0) {
-                    cancelAlarm(this);
-                } else {
-                    setNextAlarmLocked();
-                }
-            }
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmsLocked(int userId) {
-            boolean removed = false;
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                final Uptc uptc = (Uptc) alarms[i].first;
-                if (userId == uptc.userId) {
-                    mAlarmQueue.remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                setNextAlarmLocked();
-            }
-        }
-
-        @GuardedBy("mLock")
-        void removeAlarmsLocked(int userId, @NonNull String packageName) {
-            boolean removed = false;
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = alarms.length - 1; i >= 0; --i) {
-                final Uptc uptc = (Uptc) alarms[i].first;
-                if (userId == uptc.userId && packageName.equals(uptc.packageName)) {
-                    mAlarmQueue.remove(alarms[i]);
-                    removed = true;
-                }
-            }
-            if (removed) {
-                setNextAlarmLocked();
-            }
-        }
-
-        @GuardedBy("mLock")
-        private void setNextAlarmLocked() {
-            if (mAlarmQueue.size() > 0) {
-                final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
-                // Only schedule the alarm if one of the following is true:
-                // 1. There isn't one currently scheduled
-                // 2. The new alarm is significantly earlier than the previous alarm. If it's
-                // earlier but not significantly so, then we essentially delay the notification a
-                // few extra minutes.
-                if (mTriggerTimeElapsed == 0
-                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
-                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
-                    // Use a non-wakeup alarm for this
-                    scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
-                            ALARM_TAG_QUOTA_CHECK, this);
-                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
-                }
-            } else {
-                cancelAlarm(this);
-                mTriggerTimeElapsed = 0;
-            }
+    private class InQuotaAlarmQueue extends AlarmQueue<Uptc> {
+        private InQuotaAlarmQueue(Context context, Looper looper) {
+            super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false, 0);
         }
 
         @Override
-        public void onAlarm() {
-            synchronized (mLock) {
-                while (mAlarmQueue.size() > 0) {
-                    final Pair<Uptc, Long> alarm = mAlarmQueue.peek();
-                    if (alarm.second <= mInjector.getElapsedRealtime()) {
-                        getHandler().post(() -> maybeUpdateQuotaStatus(
-                                alarm.first.userId, alarm.first.packageName, alarm.first.tag));
-                        mAlarmQueue.remove(alarm);
-                    } else {
-                        break;
-                    }
-                }
-                setNextAlarmLocked();
-            }
+        protected boolean isForUser(@NonNull Uptc uptc, int userId) {
+            return userId == uptc.userId;
         }
 
-        @GuardedBy("mLock")
-        void dumpLocked(IndentingPrintWriter pw) {
-            pw.println("In quota alarms:");
-            pw.increaseIndent();
-
-            if (mAlarmQueue.size() == 0) {
-                pw.println("NOT WAITING");
-            } else {
-                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-                for (int i = 0; i < alarms.length; ++i) {
-                    final Uptc uptc = (Uptc) alarms[i].first;
-                    pw.print(uptc);
-                    pw.print(": ");
-                    pw.print(alarms[i].second);
-                    pw.println();
-                }
-            }
-
-            pw.decreaseIndent();
+        void removeAlarms(int userId, @NonNull String packageName) {
+            removeAlarmsIf((uptc) -> userId == uptc.userId && packageName.equals(uptc.packageName));
         }
 
-        @GuardedBy("mLock")
-        void dumpLocked(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(QuotaTrackerProto.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
-                    mTriggerTimeElapsed);
-
-            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
-            for (int i = 0; i < alarms.length; ++i) {
-                final long aToken = proto.start(QuotaTrackerProto.InQuotaAlarmListener.ALARMS);
-
-                final Uptc uptc = (Uptc) alarms[i].first;
-                uptc.dumpDebug(proto, QuotaTrackerProto.InQuotaAlarmListener.Alarm.UPTC);
-                proto.write(QuotaTrackerProto.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
-                        (Long) alarms[i].second);
-
-                proto.end(aToken);
+        @Override
+        protected void processExpiredAlarms(@NonNull ArraySet<Uptc> expired) {
+            for (int i = 0; i < expired.size(); ++i) {
+                Uptc uptc = expired.valueAt(i);
+                getHandler().post(
+                        () -> maybeUpdateQuotaStatus(uptc.userId, uptc.packageName, uptc.tag));
             }
-
-            proto.end(token);
         }
     }
 
@@ -635,7 +489,7 @@
             pw.println();
 
             pw.println();
-            mInQuotaAlarmListener.dumpLocked(pw);
+            mInQuotaAlarmQueue.dump(pw);
 
             pw.println();
             pw.println("Per-app free quota:");
@@ -669,7 +523,6 @@
             proto.write(QuotaTrackerProto.IS_ENABLED, mIsEnabled);
             proto.write(QuotaTrackerProto.IS_GLOBAL_QUOTA_FREE, mIsQuotaFree);
             proto.write(QuotaTrackerProto.ELAPSED_REALTIME, mInjector.getElapsedRealtime());
-            mInQuotaAlarmListener.dumpLocked(proto, QuotaTrackerProto.IN_QUOTA_ALARM_LISTENER);
         }
 
         proto.end(token);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index e190b1e..c9685702 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2260,8 +2260,17 @@
             IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId) {
         final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
         if (!hasPrivilege) {
-            mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
-                    Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
+            try {
+                mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
+                        Binder.getCallingPid(), Binder.getCallingUid(), callingPkg,
+                        callingFeatureId);
+            } catch (Exception e) {
+                // If the calling package name does not match a package installed on the system,
+                // an exception is thrown. Don't allow that exception to be thrown, otherwise,
+                // there is a difference in control flow that allows calling apps to determine
+                // if a package is installed on the device.
+                return null;
+            }
         }
 
         wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 5441414..fb337e2 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -51,6 +51,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Application;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
@@ -134,6 +135,8 @@
     private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
     private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
+    private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
+    private int mFocusedDisplay = -1;
 
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
@@ -516,6 +519,29 @@
                 + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
     }
 
+    void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
+        if (lastTarget != null) {
+            mFocusedWindow.remove(lastTarget.getDisplayId());
+        }
+        if (newTarget != null) {
+            int displayId = newTarget.getDisplayId();
+            IBinder clientBinder = newTarget.getIWindow().asBinder();
+            mFocusedWindow.put(displayId, clientBinder);
+        }
+    }
+
+    public void onDisplayRemoved(int displayId) {
+        mFocusedWindow.remove(displayId);
+    }
+
+    public void setFocusedDisplay(int focusedDisplayId) {
+        mFocusedDisplay = focusedDisplayId;
+    }
+
+    @Nullable IBinder getFocusedWindowToken() {
+        return mFocusedWindow.get(mFocusedDisplay);
+    }
+
     /**
      * This class encapsulates the functionality related to display magnification.
      */
@@ -1838,36 +1864,11 @@
                     tempWindowStatesList.add(w);
                 }
             }, false /* traverseTopToBottom */);
-            // Insert the re-parented windows in another display below their parents in
-            // default display.
-            mService.mRoot.forAllWindows(w -> {
-                final WindowState parentWindow = findRootDisplayParentWindow(w);
-                if (parentWindow == null) {
-                    return;
-                }
-
-                if (w.isVisible() && tempWindowStatesList.contains(parentWindow)) {
-                    tempWindowStatesList.add(tempWindowStatesList.lastIndexOf(parentWindow), w);
-                }
-            }, false /* traverseTopToBottom */);
             for (int i = 0; i < tempWindowStatesList.size(); i++) {
                 outWindows.put(i, tempWindowStatesList.get(i));
             }
         }
 
-        private WindowState findRootDisplayParentWindow(WindowState win) {
-            WindowState displayParentWindow = win.getDisplayContent().getParentWindow();
-            if (displayParentWindow == null) {
-                return null;
-            }
-            WindowState candidate = displayParentWindow;
-            while (candidate != null) {
-                displayParentWindow = candidate;
-                candidate = displayParentWindow.getDisplayContent().getParentWindow();
-            }
-            return displayParentWindow;
-        }
-
         private WindowState getTopFocusWindow() {
             return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
new file mode 100644
index 0000000..1c2333a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback to intercept activity starts and possibly block/redirect them.
+ */
+public abstract class ActivityInterceptorCallback {
+    /**
+     * Intercept the launch intent based on various signals. If an interception happened, returns
+     * a new/existing non-null {@link Intent} which may redirect to another activity.
+     *
+     * @return null if no interception occurred, or a non-null intent which replaces the
+     * existing intent.
+     */
+    public abstract @Nullable Intent intercept(ActivityInterceptorInfo info);
+
+    /**
+     * The unique id of each interceptor which determines the order it will execute in.
+     */
+    @IntDef(suffix = { "_ORDERED_ID" }, value = {
+            FIRST_ORDERED_ID,
+            LAST_ORDERED_ID // Update this when adding new ids
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OrderedId {}
+
+    /**
+     * The first id, used by the framework to determine the valid range of ids.
+     */
+    static final int FIRST_ORDERED_ID = 0;
+
+    /**
+     * The final id, used by the framework to determine the valid range of ids. Update this when
+     * adding new ids.
+     */
+    static final int LAST_ORDERED_ID = FIRST_ORDERED_ID;
+
+    /**
+     * Data class for storing the various arguments needed for activity interception.
+     */
+    public static final class ActivityInterceptorInfo {
+        public final int realCallingUid;
+        public final int realCallingPid;
+        public final int userId;
+        public final String callingPackage;
+        public final String callingFeatureId;
+        public final Intent intent;
+        public final ResolveInfo rInfo;
+        public final ActivityInfo aInfo;
+        public final String resolvedType;
+        public final int callingPid;
+        public final int callingUid;
+        public final ActivityOptions checkedOptions;
+
+        public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId,
+                String callingPackage, String callingFeatureId, Intent intent,
+                ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid,
+                int callingUid, ActivityOptions checkedOptions) {
+            this.realCallingUid = realCallingUid;
+            this.realCallingPid = realCallingPid;
+            this.userId = userId;
+            this.callingPackage = callingPackage;
+            this.callingFeatureId = callingFeatureId;
+            this.intent = intent;
+            this.rInfo = rInfo;
+            this.aInfo = aInfo;
+            this.resolvedType = resolvedType;
+            this.callingPid = callingPid;
+            this.callingUid = callingUid;
+            this.checkedOptions = checkedOptions;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b807863..d81eada 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -399,10 +399,6 @@
     private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
     private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
 
-    /**
-     * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
-     */
-    @VisibleForTesting static final int Z_BOOST_BASE = 800570000;
     static final int INVALID_PID = -1;
 
     // How long we wait until giving up on the last activity to pause.  This
@@ -2403,17 +2399,6 @@
         });
     }
 
-    void removeStartingWindowIfNeeded() {
-        // Removing the task snapshot after the task is actually focused (see
-        // Task#onWindowFocusChanged). Since some of the app contents may draw in this time and
-        // requires more times to draw finish, in case flicking may happen when removing the task
-        // snapshot too early. (i.e. Showing IME.)
-        if ((mStartingData instanceof SnapshotStartingData) && !getTask().isFocused()) {
-            return;
-        }
-        removeStartingWindow();
-    }
-
     void removeStartingWindow() {
         if (transferSplashScreenIfNeeded()) {
             return;
@@ -4253,20 +4238,6 @@
         return callback.test(this) ? this : null;
     }
 
-    @Override
-    protected void setLayer(Transaction t, int layer) {
-        if (!mSurfaceAnimator.hasLeash()) {
-            t.setLayer(mSurfaceControl, layer);
-        }
-    }
-
-    @Override
-    protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
-        if (!mSurfaceAnimator.hasLeash()) {
-            t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
-        }
-    }
-
     void logStartActivity(int tag, Task task) {
         final Uri data = intent.getData();
         final String strData = data != null ? data.toSafeString() : null;
@@ -6079,13 +6050,13 @@
         final Task associatedTask =
                 mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
         if (associatedTask == null) {
-            removeStartingWindowIfNeeded();
+            removeStartingWindow();
         } else if (associatedTask.getActivity(
                 r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
-                r.removeStartingWindowIfNeeded();
+                r.removeStartingWindow();
             }
         }
         updateReportedVisibilityLocked();
@@ -6796,12 +6767,6 @@
         return candidate;
     }
 
-    SurfaceControl getAppAnimationLayer() {
-        return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME
-                : needsZBoost() ? ANIMATION_LAYER_BOOSTED
-                        : ANIMATION_LAYER_STANDARD);
-    }
-
     @Override
     boolean needsZBoost() {
         return mNeedsZBoost || super.needsZBoost();
@@ -6862,29 +6827,9 @@
                 || mDisplayContent.isNextTransitionForward();
     }
 
-    private int getAnimationLayer() {
-        // The leash is parented to the animation layer. We need to preserve the z-order by using
-        // the prefix order index, but we boost if necessary.
-        int layer;
-        if (!inPinnedWindowingMode()) {
-            layer = getPrefixOrderIndex();
-        } else {
-            // Root pinned tasks have animations take place within themselves rather than an
-            // animation layer so we need to preserve the order relative to the root task (e.g.
-            // the order of our task/parent).
-            layer = getParent().getPrefixOrderIndex();
-        }
-
-        if (mNeedsZBoost) {
-            layer += Z_BOOST_BASE;
-        }
-        return layer;
-    }
-
     @Override
-    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
-        t.setLayer(leash, getAnimationLayer());
-        getDisplayContent().assignRootTaskOrdering();
+    void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
+        // Noop as Activity may be offset for letterbox
     }
 
     @Override
@@ -6913,7 +6858,7 @@
 
             // Crop to root task bounds.
             t.setLayer(leash, 0);
-            t.setLayer(mAnimationBoundsLayer, getAnimationLayer());
+            t.setLayer(mAnimationBoundsLayer, getLastLayer());
 
             // Reparent leash to animation bounds layer.
             t.reparent(leash, mAnimationBoundsLayer);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 979cea9..223f0be 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -51,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppActivity;
@@ -178,7 +179,33 @@
             // before issuing the work challenge.
             return true;
         }
-        return interceptLockedManagedProfileIfNeeded();
+        if (interceptLockedManagedProfileIfNeeded()) {
+            return true;
+        }
+
+        final SparseArray<ActivityInterceptorCallback> callbacks =
+                mService.getActivityInterceptorCallbacks();
+        final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo =
+                new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
+                        mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
+                        mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
+                        mActivityOptions);
+
+        for (int i = 0; i < callbacks.size(); i++) {
+            final ActivityInterceptorCallback callback = callbacks.valueAt(i);
+            final Intent newIntent = callback.intercept(interceptorInfo);
+            if (newIntent == null) {
+                continue;
+            }
+            mIntent = newIntent;
+            mCallingPid = mRealCallingPid;
+            mCallingUid = mRealCallingUid;
+            mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid);
+            mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags,
+                    null /*profilerInfo*/);
+            return true;
+        }
+        return false;
     }
 
     private boolean hasCrossProfileAnimation() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 9db13ba..960fa50 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -37,6 +37,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.window.TaskSnapshot;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.am.UserState;
@@ -631,7 +632,8 @@
         @Nullable
         public final LocaleList mLocales;
 
-        PackageConfig(Integer nightMode, LocaleList locales) {
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public PackageConfig(Integer nightMode, LocaleList locales) {
             mNightMode = nightMode;
             mLocales = locales;
         }
@@ -678,4 +680,12 @@
 
     /** Called when the device is waking up */
     public abstract void notifyWakingUp();
+
+    /**
+     * Registers a callback which can intercept activity starts.
+     * @throws IllegalArgumentException if duplicate ids are provided
+     */
+    public abstract void registerActivityStartInterceptor(
+            @ActivityInterceptorCallback.OrderedId int id,
+            ActivityInterceptorCallback callback);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 41928fc..b11cb93 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -92,6 +92,8 @@
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
 import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
 import static com.android.server.am.EventLogTags.writeConfigurationChanged;
+import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -457,6 +459,8 @@
     /** The controller for all operations related to locktask. */
     private LockTaskController mLockTaskController;
     private ActivityStartController mActivityStartController;
+    private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks =
+            new SparseArray<>();
     PackageConfigPersister mPackageConfigPersister;
 
     boolean mSuppressResizeConfigChanges;
@@ -1114,6 +1118,10 @@
         return mBackgroundActivityStartCallback;
     }
 
+    SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() {
+        return mActivityInterceptorCallbacks;
+    }
+
     private void start() {
         LocalServices.addService(ActivityTaskManagerInternal.class, mInternal);
     }
@@ -3414,9 +3422,15 @@
                 final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
                 mRootWindowContainer.moveActivityToPinnedRootTask(
                         r, "enterPictureInPictureMode");
-                final Task rootTask = r.getRootTask();
-                rootTask.setPictureInPictureAspectRatio(aspectRatio);
-                rootTask.setPictureInPictureActions(actions);
+                final Task task = r.getTask();
+                task.setPictureInPictureAspectRatio(aspectRatio);
+                task.setPictureInPictureActions(actions);
+
+                // Continue the pausing process after entering pip.
+                if (task.getPausingActivity() == r) {
+                    task.schedulePauseActivity(r, false /* userLeaving */,
+                            false /* pauseImmediately */, "auto-pip");
+                }
             }
         };
 
@@ -4737,7 +4751,7 @@
                                     mContext.getText(R.string.heavy_weight_notification_detail))
                             .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
                                     intent, PendingIntent.FLAG_CANCEL_CURRENT
-                                    | PendingIntent.FLAG_IMMUTABLE, null,
+                                            | PendingIntent.FLAG_IMMUTABLE, null,
                                     new UserHandle(userId)))
                             .build();
             try {
@@ -6577,5 +6591,22 @@
             getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
                     null /* trigger */, mRootWindowContainer.getDefaultDisplay());
         }
+
+        @Override
+        public void registerActivityStartInterceptor(
+                @ActivityInterceptorCallback.OrderedId int id,
+                ActivityInterceptorCallback callback) {
+            synchronized (mGlobalLock) {
+                if (mActivityInterceptorCallbacks.contains(id)) {
+                    throw new IllegalArgumentException("Duplicate id provided: " + id);
+                }
+                if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) {
+                    throw new IllegalArgumentException(
+                            "Provided id " + id + " is not in range of valid ids ["
+                                    + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]");
+                }
+                mActivityInterceptorCallbacks.put(id, callback);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index bf66107..91f650f 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -32,7 +32,6 @@
 
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.CriticalEventLog;
-import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -82,21 +81,23 @@
         final boolean aboveSystem;
         final ActivityRecord activity;
         synchronized (mService.mGlobalLock) {
-            WindowState windowState = mService.mInputToWindowMap.get(inputToken);
-            if (windowState != null) {
-                pid = windowState.mSession.mPid;
-                activity = windowState.mActivityRecord;
-                Slog.i(TAG_WM, "ANR in " + windowState.mAttrs.getTitle() + ". Reason:" + reason);
-            } else {
-                EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken);
-                if (embeddedWindow == null) {
-                    Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
-                    return;
-                }
-                pid = embeddedWindow.mOwnerPid;
-                windowState = embeddedWindow.mHostWindowState;
-                activity = null; // Don't blame the host process, instead blame the embedded pid.
+            InputTarget target = mService.getInputTargetFromToken(inputToken);
+            if (target == null) {
+                Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
+                return;
             }
+
+            WindowState windowState = target.asWindowState();
+            pid = target.getPid();
+            if (windowState != null) {
+                activity = windowState.mActivityRecord;
+            } else {
+                // Don't blame the host process, instead blame the embedded pid.
+                activity = null;
+                // Use host WindowState for logging and z-order test.
+                windowState = target.asEmbeddedWindow().mHostWindowState;
+            }
+            Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
             aboveSystem = isWindowAboveSystem(windowState);
             dumpAnrStateLocked(activity, windowState, reason);
         }
@@ -110,19 +111,12 @@
     void notifyWindowResponsive(IBinder inputToken) {
         final int pid;
         synchronized (mService.mGlobalLock) {
-            WindowState windowState = mService.mInputToWindowMap.get(inputToken);
-            if (windowState != null) {
-                pid = windowState.mSession.mPid;
-            } else {
-                // Check if the token belongs to an embedded window.
-                EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken);
-                if (embeddedWindow == null) {
-                    Slog.e(TAG_WM,
-                            "Unknown token, dropping notifyWindowConnectionResponsive request");
-                    return;
-                }
-                pid = embeddedWindow.mOwnerPid;
+            InputTarget target = mService.getInputTargetFromToken(inputToken);
+            if (target == null) {
+                Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request");
+                return;
             }
+            pid = target.getPid();
         }
         mService.mAmInternal.inputDispatchingResumed(pid);
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 31126f5..5e6f234 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -467,12 +467,6 @@
     @VisibleForTesting
     boolean isDefaultDisplay;
 
-    /**
-     * Flag indicating whether WindowManager should override info for this display in
-     * DisplayManager.
-     */
-    boolean mShouldOverrideDisplayConfiguration = true;
-
     /** Window tokens that are in the process of exiting, but still on screen for animations. */
     final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
 
@@ -655,11 +649,6 @@
     private final InsetsStateController mInsetsStateController;
     private final InsetsPolicy mInsetsPolicy;
 
-    /** @see #getParentWindow() */
-    private WindowState mParentWindow;
-
-    private Point mLocationInParentWindow = new Point();
-
     /** Corner radius that windows should have in order to match the display. */
     private final float mWindowCornerRadius;
 
@@ -860,7 +849,6 @@
                     w.updateLastFrames();
                 }
                 w.onResizeHandled();
-                w.updateLocationInParentDisplayIfNeeded();
             }
 
             if (w.mActivityRecord != null) {
@@ -1067,7 +1055,7 @@
 
         final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
                 "PointerEventDispatcher" + mDisplayId, mDisplayId);
-        mPointerEventDispatcher = new PointerEventDispatcher(inputChannel, this);
+        mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
 
         // Tap Listeners are supported for:
         // 1. All physical displays (multi-display).
@@ -1661,7 +1649,7 @@
     }
 
     /** Returns {@code true} if the IME is possible to show on the launching activity. */
-    private boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
+    boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
         final WindowState win = r.findMainWindow();
         if (win == null) {
             return false;
@@ -2023,14 +2011,8 @@
         computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh,
                 mDisplayMetrics.density, outConfig);
 
-        // We usually set the override info in DisplayManager so that we get consistent display
-        // metrics values when displays are changing and don't send out new values until WM is aware
-        // of them. However, we don't do this for displays that serve as containers for ActivityView
-        // because we don't want letter-/pillar-boxing during resize.
-        final DisplayInfo overrideDisplayInfo = mShouldOverrideDisplayConfiguration
-                ? mDisplayInfo : null;
         mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
-                overrideDisplayInfo);
+                mDisplayInfo);
 
         mBaseDisplayRect.set(0, 0, dw, dh);
 
@@ -3075,9 +3057,6 @@
     void removeImmediately() {
         mDeferredRemoval = false;
         try {
-            if (mParentWindow != null) {
-                mParentWindow.removeEmbeddedDisplayContent(this);
-            }
             // Clear all transitions & screen frozen states when removing display.
             mOpeningApps.clear();
             mClosingApps.clear();
@@ -3094,6 +3073,7 @@
             mOverlayLayer.release();
             mInputMonitor.onDisplayRemoved();
             mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
+            mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
         } finally {
             mDisplayReady = false;
         }
@@ -3804,7 +3784,11 @@
     }
 
     boolean shouldImeAttachedToApp() {
-        return isImeControlledByApp()
+        // Force attaching IME to the display when magnifying, or it would be magnified with
+        // target app together.
+        final boolean allowAttachToApp = (mMagnificationSpec == null);
+
+        return allowAttachToApp && isImeControlledByApp()
                 && mImeLayeringTarget != null
                 && mImeLayeringTarget.mActivityRecord != null
                 && mImeLayeringTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
@@ -3829,8 +3813,7 @@
 
     /**
      * Finds the window which can host IME if IME target cannot host it.
-     * e.g. IME target cannot host IME when it's display has a parent display OR when display
-     * doesn't support IME/system decorations.
+     * e.g. IME target cannot host IME  when display doesn't support IME/system decorations.
      *
      * @param target current IME target.
      * @return {@link InsetsControlTarget} that can host IME.
@@ -4143,14 +4126,10 @@
      */
     @VisibleForTesting
     SurfaceControl computeImeParent() {
-        // Force attaching IME to the display when magnifying, or it would be magnified with
-        // target app together.
-        final boolean allowAttachToApp = (mMagnificationSpec == null);
-
         // Attach it to app if the target is part of an app and such app is covering the entire
         // screen. If it's not covering the entire screen the IME might extend beyond the apps
         // bounds.
-        if (allowAttachToApp && shouldImeAttachedToApp()) {
+        if (shouldImeAttachedToApp()) {
             if (mImeLayeringTarget.mActivityRecord != mImeInputTarget.mActivityRecord) {
                 // Do not change parent if the window hasn't requested IME.
                 return null;
@@ -5184,65 +5163,6 @@
                 && isTrusted();
     }
 
-    /**
-     * Get the window which owns the surface that this DisplayContent is re-parented to.
-     *
-     * @return the parent window.
-     */
-    WindowState getParentWindow() {
-        return mParentWindow;
-    }
-
-    /**
-     * Update the location of this display in the parent window. This enables windows in this
-     * display to compute the global transformation matrix.
-     *
-     * @param win The parent window of this display.
-     * @param x The x coordinate in the parent window.
-     * @param y The y coordinate in the parent window.
-     */
-    void updateLocation(WindowState win, int x, int y) {
-        if (mParentWindow != win) {
-            throw new IllegalArgumentException(
-                    "The given window is not the parent window of this display.");
-        }
-        if (!mLocationInParentWindow.equals(x, y)) {
-            mLocationInParentWindow.set(x, y);
-            if (mWmService.mAccessibilityController.hasCallbacks()) {
-                mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(mDisplayId);
-            }
-            notifyLocationInParentDisplayChanged();
-        }
-    }
-
-    Point getLocationInParentWindow() {
-        return mLocationInParentWindow;
-    }
-
-    Point getLocationInParentDisplay() {
-        final Point location = new Point();
-        if (mParentWindow != null) {
-            // LocationInParentWindow indicates the offset to (0,0) of window, but what we need is
-            // the offset to (0,0) of display.
-            DisplayContent dc = this;
-            do {
-                final WindowState displayParent = dc.getParentWindow();
-                location.x += displayParent.getFrame().left
-                        + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
-                location.y += displayParent.getFrame().top
-                        + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
-                dc = displayParent.getDisplayContent();
-            } while (dc != null && dc.getParentWindow() != null);
-        }
-        return location;
-    }
-
-    void notifyLocationInParentDisplayChanged() {
-        forAllWindows(w -> {
-            w.updateLocationInParentDisplayIfNeeded();
-        }, false /* traverseTopToBottom */);
-    }
-
     SurfaceControl getWindowingLayer() {
         return mWindowingLayer;
     }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index b08d6e1..dcd1148 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -127,7 +127,7 @@
         }
     }
 
-    static class EmbeddedWindow {
+    static class EmbeddedWindow implements InputTarget {
         final IWindow mClient;
         @Nullable final WindowState mHostWindowState;
         @Nullable final ActivityRecord mHostActivityRecord;
@@ -166,7 +166,8 @@
             mDisplayId = displayId;
         }
 
-        String getName() {
+        @Override
+        public String toString() {
             final String hostWindowName = (mHostWindowState != null)
                     ? mHostWindowState.getWindowTag().toString() : "Internal";
             return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName
@@ -183,7 +184,7 @@
         }
 
         InputChannel openInputChannel() {
-            final String name = getName();
+            final String name = toString();
             mInputChannel = mWmService.mInputManager.createInputChannel(name);
             return mInputChannel;
         }
@@ -195,5 +196,25 @@
                 mInputChannel = null;
             }
         }
+
+        @Override
+        public EmbeddedWindow asEmbeddedWindow() {
+            return this;
+        }
+
+        @Override
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        @Override
+        public IWindow getIWindow() {
+            return mClient;
+        }
+
+        @Override
+        public int getPid() {
+            return mOwnerPid;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
new file mode 100644
index 0000000..fec7cc9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.view.IWindow;
+
+/**
+ * Common interface between focusable objects.
+ *
+ * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties
+ * of both targets.
+ */
+interface InputTarget {
+    default WindowState asWindowState() {
+        return null;
+    }
+
+    default EmbeddedWindowController.EmbeddedWindow asEmbeddedWindow() {
+        return null;
+    }
+
+    /* Display id of the target. */
+    int getDisplayId();
+
+    /* Client IWindow for the target. */
+    IWindow getIWindow();
+
+    /* Owning pid of the target. */
+    int getPid();
+}
+
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
index 9bc7e93..4f8ec63 100644
--- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -16,15 +16,11 @@
 
 package com.android.server.wm;
 
-import static com.android.server.input.InputManagerService.ENABLE_PER_WINDOW_INPUT_ROTATION;
-
-import android.graphics.Point;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.MotionEvent;
-import android.view.Surface;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
 import com.android.server.UiThread;
@@ -35,12 +31,8 @@
     private final ArrayList<PointerEventListener> mListeners = new ArrayList<>();
     private PointerEventListener[] mListenersArray = new PointerEventListener[0];
 
-    private final DisplayContent mDisplayContent;
-    private final Point mTmpSize = new Point();
-
-    public PointerEventDispatcher(InputChannel inputChannel, DisplayContent dc) {
+    public PointerEventDispatcher(InputChannel inputChannel) {
         super(inputChannel, UiThread.getHandler().getLooper());
-        mDisplayContent = dc;
     }
 
     @Override
@@ -49,15 +41,6 @@
             if (event instanceof MotionEvent
                     && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                 MotionEvent motionEvent = (MotionEvent) event;
-                if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
-                    final int rotation = mDisplayContent.getRotation();
-                    if (rotation != Surface.ROTATION_0) {
-                        mDisplayContent.getDisplay().getRealSize(mTmpSize);
-                        motionEvent = MotionEvent.obtain(motionEvent);
-                        motionEvent.transform(MotionEvent.createRotateMatrix(
-                                rotation, mTmpSize.x, mTmpSize.y));
-                    }
-                }
                 PointerEventListener[] listeners;
                 synchronized (mListeners) {
                     if (mListenersArray == null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 97ea41c..1a881f7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -504,6 +504,7 @@
             mTopFocusedDisplayId = topFocusedDisplayId;
             mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
             mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
+            mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
             ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
         }
         return changed;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 8c056b2..6b7f92a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -620,11 +620,6 @@
     }
 
     @Override
-    public void updateDisplayContentLocation(IWindow window, int x, int y, int displayId) {
-        mService.updateDisplayContentLocation(window, x, y, displayId);
-    }
-
-    @Override
     public void updateTapExcludeRegion(IWindow window, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0819549..3cb80d6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2972,11 +2972,6 @@
         return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
     }
 
-    @Override
-    void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
-        super.resetSurfacePositionForAnimationLeash(t);
-    }
-
     boolean shouldAnimate() {
         /**
          * Animations are handled by the TaskOrganizer implementation.
@@ -4270,7 +4265,7 @@
     /**
      * @return true if the task is currently focused.
      */
-    boolean isFocused() {
+    private boolean isFocused() {
         if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
             return false;
         }
@@ -4328,14 +4323,10 @@
     }
 
     /**
-     * Called on the task of a window which gained or lost focus.
+     * Called on the task when it gained or lost focus.
      * @param hasFocus
      */
     void onAppFocusChanged(boolean hasFocus) {
-        final ActivityRecord topAct = getTopVisibleActivity();
-        if (topAct != null && (topAct.mStartingData instanceof SnapshotStartingData)) {
-            topAct.removeStartingWindowIfNeeded();
-        }
         updateShadowsRadius(hasFocus, getSyncTransaction());
         dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 66f2dbc..71844ce 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -99,13 +99,6 @@
     private int mColorLayerCounter = 0;
 
     /**
-     * A control placed at the appropriate level for transitions to occur.
-     */
-    private SurfaceControl mAppAnimationLayer;
-    private SurfaceControl mBoostedAppAnimationLayer;
-    private SurfaceControl mHomeAppAnimationLayer;
-
-    /**
      * Given that the split-screen divider does not have an AppWindowToken, it
      * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
      * it will need to be interleaved with some of our children, appearing on top of
@@ -132,7 +125,6 @@
     private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>();
     private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>();
     private final IntArray mTmpNeedsZBoostIndexes = new IntArray();
-    private int mTmpLayerForAnimationLayer;
 
     private ArrayList<Task> mTmpTasks = new ArrayList<>();
 
@@ -871,33 +863,13 @@
 
         int layer = 0;
         // Place root home tasks to the bottom.
-        layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer, false /* normalRootTasks */);
-        // The home animation layer is between the root home tasks and the normal root tasks.
-        final int layerForHomeAnimationLayer = layer++;
-        mTmpLayerForAnimationLayer = layer++;
-        layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer, true /* normalRootTasks */);
+        layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
+        adjustRootTaskLayer(t, mTmpNormalChildren, layer);
 
-        // The boosted animation layer is between the normal root tasks and the always on top
-        // root tasks.
-        final int layerForBoostedAnimationLayer = layer++;
         // Always on top tasks layer should higher than split divider layer so set it as start.
-        layer = SPLIT_DIVIDER_LAYER + 1;
-        adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer, false /* normalRootTasks */);
-
-        t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
-        t.setLayer(mAppAnimationLayer, mTmpLayerForAnimationLayer);
         t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
-        t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
-    }
-
-    private int adjustNormalRootTaskLayer(WindowContainer child, int layer) {
-        if ((child.asTask() != null && child.asTask().isAnimatingByRecents())
-                || child.isAppTransitioning()) {
-            // The animation layer is located above the highest animating root task and no
-            // higher.
-            mTmpLayerForAnimationLayer = layer++;
-        }
-        return layer;
+        layer = SPLIT_DIVIDER_LAYER + 1;
+        adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
     }
 
     /**
@@ -906,11 +878,10 @@
      * normal rootTasks.
      *
      * @param startLayer   The beginning layer of this group of rootTasks.
-     * @param normalRootTasks Set {@code true} if this group is neither home nor always on top.
      * @return The adjusted layer value.
      */
     private int adjustRootTaskLayer(SurfaceControl.Transaction t,
-            ArrayList<WindowContainer> children, int startLayer, boolean normalRootTasks) {
+            ArrayList<WindowContainer> children, int startLayer) {
         mTmpNeedsZBoostIndexes.clear();
         final int childCount = children.size();
         for (int i = 0; i < childCount; i++) {
@@ -923,9 +894,6 @@
 
             if (!childNeedsZBoost) {
                 child.assignLayer(t, startLayer++);
-                if (normalRootTasks) {
-                    startLayer = adjustNormalRootTaskLayer(child, startLayer);
-                }
             } else {
                 mTmpNeedsZBoostIndexes.add(i);
             }
@@ -935,9 +903,6 @@
         for (int i = 0; i < zBoostSize; i++) {
             final WindowContainer child = children.get(mTmpNeedsZBoostIndexes.get(i));
             child.assignLayer(t, startLayer++);
-            if (normalRootTasks) {
-                startLayer = adjustNormalRootTaskLayer(child, startLayer);
-            }
         }
         return startLayer;
     }
@@ -951,19 +916,6 @@
     }
 
     @Override
-    SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
-        switch (animationLayer) {
-            case ANIMATION_LAYER_BOOSTED:
-                return mBoostedAppAnimationLayer;
-            case ANIMATION_LAYER_HOME:
-                return mHomeAppAnimationLayer;
-            case ANIMATION_LAYER_STANDARD:
-            default:
-                return mAppAnimationLayer;
-        }
-    }
-
-    @Override
     RemoteAnimationTarget createRemoteAnimationTarget(
             RemoteAnimationController.RemoteAnimationRecord record) {
         final ActivityRecord activity = getTopMostActivity();
@@ -983,42 +935,21 @@
                         .setName("colorBackgroundLayer")
                         .setCallsite("TaskDisplayArea.onParentChanged")
                         .build();
-                mAppAnimationLayer = makeChildSurface(null)
-                        .setName("animationLayer")
-                        .setCallsite("TaskDisplayArea.onParentChanged")
-                        .build();
-                mBoostedAppAnimationLayer = makeChildSurface(null)
-                        .setName("boostedAnimationLayer")
-                        .setCallsite("TaskDisplayArea.onParentChanged")
-                        .build();
-                mHomeAppAnimationLayer = makeChildSurface(null)
-                        .setName("homeAnimationLayer")
-                        .setCallsite("TaskDisplayArea.onParentChanged")
-                        .build();
                 mSplitScreenDividerAnchor = makeChildSurface(null)
                         .setName("splitScreenDividerAnchor")
                         .setCallsite("TaskDisplayArea.onParentChanged")
                         .build();
 
                 getSyncTransaction()
-                        .show(mAppAnimationLayer)
-                        .show(mBoostedAppAnimationLayer)
-                        .show(mHomeAppAnimationLayer)
                         .show(mSplitScreenDividerAnchor);
             });
         } else {
             super.onParentChanged(newParent, oldParent);
             mWmService.mTransactionFactory.get()
                     .remove(mColorBackgroundLayer)
-                    .remove(mAppAnimationLayer)
-                    .remove(mBoostedAppAnimationLayer)
-                    .remove(mHomeAppAnimationLayer)
                     .remove(mSplitScreenDividerAnchor)
                     .apply();
             mColorBackgroundLayer = null;
-            mAppAnimationLayer = null;
-            mBoostedAppAnimationLayer = null;
-            mHomeAppAnimationLayer = null;
             mSplitScreenDividerAnchor = null;
         }
     }
@@ -1059,15 +990,12 @@
     @Override
     void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
         super.migrateToNewSurfaceControl(t);
-        if (mAppAnimationLayer == null) {
+        if (mColorBackgroundLayer == null) {
             return;
         }
 
         // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
         t.reparent(mColorBackgroundLayer, mSurfaceControl);
-        t.reparent(mAppAnimationLayer, mSurfaceControl);
-        t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
-        t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
         t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
         reassignLayer(t);
         scheduleAnimation();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 44b22c6..99f6f34 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1428,23 +1428,8 @@
                         + "directly: %s", prev);
 
                 didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
-                mPausingActivity = null;
             } else {
-                ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
-                try {
-                    EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
-                            prev.shortComponentName, "userLeaving=" + userLeaving, reason);
-
-                    mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
-                            prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
-                                    prev.configChangeFlags, pauseImmediately));
-                } catch (Exception e) {
-                    // Ignore exception, if process died other code will cleanup.
-                    Slog.w(TAG, "Exception thrown during pause", e);
-                    mPausingActivity = null;
-                    mLastPausedActivity = null;
-                    mTaskSupervisor.mNoHistoryActivities.remove(prev);
-                }
+                schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
             }
         } else {
             mPausingActivity = null;
@@ -1459,7 +1444,7 @@
         }
 
         // If already entered PIP mode, no need to keep pausing.
-        if (mPausingActivity != null && !didAutoPip) {
+        if (mPausingActivity != null) {
             // Have the window manager pause its key dispatching until the new
             // activity has started.  If we're pausing the activity just because
             // the screen is being turned off and the UI is sleeping, don't interrupt
@@ -1494,6 +1479,25 @@
         }
     }
 
+    void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
+            boolean pauseImmediately, String reason) {
+        ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
+        try {
+            EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
+                    prev.shortComponentName, "userLeaving=" + userLeaving, reason);
+
+            mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
+                    prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
+                            prev.configChangeFlags, pauseImmediately));
+        } catch (Exception e) {
+            // Ignore exception, if process died other code will cleanup.
+            Slog.w(TAG, "Exception thrown during pause", e);
+            mPausingActivity = null;
+            mLastPausedActivity = null;
+            mTaskSupervisor.mNoHistoryActivities.remove(prev);
+        }
+    }
+
     @VisibleForTesting
     void completePause(boolean resumeNext, ActivityRecord resuming) {
         // Complete the pausing process of a pausing activity, so it doesn't make sense to
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9cc24e2..e31a662 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -261,8 +261,7 @@
             if (launchMode == WINDOWING_MODE_PINNED) {
                 if (DEBUG) appendLog("picture-in-picture");
             } else if (!root.isResizeable()) {
-                if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea,
-                        options.getLaunchWindowingMode())) {
+                if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) {
                     launchMode = WINDOWING_MODE_FREEFORM;
                     if (outParams.mBounds.isEmpty()) {
                         getTaskBounds(root, suggestedDisplayArea, layout, launchMode,
@@ -618,8 +617,8 @@
     }
 
     private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
-            TaskDisplayArea displayArea, int launchWindowingMode) {
-        if (launchWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            TaskDisplayArea displayArea, @Nullable ActivityOptions options) {
+        if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
             // Do not launch the activity in freeform if it explicitly requested fullscreen mode.
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index c9f2696..731036b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -28,7 +28,6 @@
 import android.app.WindowConfiguration;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -40,6 +39,7 @@
 import android.window.ITaskOrganizerController;
 import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
@@ -505,25 +505,28 @@
         if (lastOrganizer == null) {
             return;
         }
-        SurfaceControl windowAnimationLeash = null;
-        Rect mainFrame = null;
+        final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+        removalInfo.taskId = task.mTaskId;
+        removalInfo.playRevealAnimation = prepareAnimation;
         final boolean playShiftUpAnimation = !task.inMultiWindowMode();
-        if (prepareAnimation && playShiftUpAnimation) {
-            final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
-            if (topActivity != null) {
+        final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
+        if (topActivity != null) {
+            removalInfo.deferRemoveForIme = topActivity.mDisplayContent
+                    .mayImeShowOnLaunchingActivity(topActivity);
+            if (prepareAnimation && playShiftUpAnimation) {
                 final WindowState mainWindow =
                         topActivity.findMainWindow(false/* includeStartingApp */);
                 if (mainWindow != null) {
                     final SurfaceControl.Transaction t = mainWindow.getPendingTransaction();
-                    windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
-                    mainFrame = mainWindow.getRelativeFrame();
-                    t.setPosition(windowAnimationLeash, mainFrame.left, mainFrame.top);
+                    removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+                    removalInfo.mainFrame = mainWindow.getRelativeFrame();
+                    t.setPosition(removalInfo.windowAnimationLeash,
+                            removalInfo.mainFrame.left, removalInfo.mainFrame.top);
                 }
             }
         }
         try {
-            lastOrganizer.removeStartingWindow(task.mTaskId, windowAnimationLeash,
-                    mainFrame, prepareAnimation);
+            lastOrganizer.removeStartingWindow(removalInfo);
         } catch (RemoteException e) {
             Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6b21858..9a4bf63 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -71,7 +71,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
-import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -128,26 +127,6 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
 
-    /** Animation layer that happens above all animating {@link Task}s. */
-    static final int ANIMATION_LAYER_STANDARD = 0;
-
-    /** Animation layer that happens above all {@link Task}s. */
-    static final int ANIMATION_LAYER_BOOSTED = 1;
-
-    /**
-     * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME}
-     * activities and all activities that are being controlled by the recents animation. This
-     * layer is generally below all {@link Task}s.
-     */
-    static final int ANIMATION_LAYER_HOME = 2;
-
-    @IntDef(prefix = { "ANIMATION_LAYER_" }, value = {
-            ANIMATION_LAYER_STANDARD,
-            ANIMATION_LAYER_BOOSTED,
-            ANIMATION_LAYER_HOME,
-    })
-    @interface AnimationLayer {}
-
     static final int POSITION_TOP = Integer.MAX_VALUE;
     static final int POSITION_BOTTOM = Integer.MIN_VALUE;
 
@@ -2672,17 +2651,6 @@
         return getParentSurfaceControl();
     }
 
-    /**
-     * @return The layer on which all app animations are happening.
-     */
-    SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
-        final WindowContainer parent = getParent();
-        if (parent != null) {
-            return parent.getAppAnimationLayer(animationLayer);
-        }
-        return null;
-    }
-
     // TODO: Remove this and use #getBounds() instead once we set an app transition animation
     // on TaskStack.
     Rect getAnimationBounds(int appRootTaskClipMode) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 553e982..cf10e70 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4982,22 +4982,47 @@
         return Surface.ROTATION_0;
     }
 
-    void reportFocusChanged(IBinder oldToken, IBinder newToken) {
-        WindowState lastFocus;
-        WindowState newFocus;
-        synchronized (mGlobalLock) {
-            lastFocus = mInputToWindowMap.get(oldToken);
-            newFocus = mInputToWindowMap.get(newToken);
-            ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastFocus, newFocus);
+    // Returns an input target which is mapped to the given input token. This can be a WindowState
+    // or an embedded window.
+    @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) {
+        WindowState windowState = mInputToWindowMap.get(inputToken);
+        if (windowState != null) {
+            return windowState;
         }
 
-        if (newFocus != null) {
+        EmbeddedWindowController.EmbeddedWindow embeddedWindow =
+                mEmbeddedWindowController.get(inputToken);
+        if (embeddedWindow != null) {
+            return embeddedWindow;
+        }
+
+        return null;
+    }
+
+    void reportFocusChanged(IBinder oldToken, IBinder newToken) {
+        InputTarget lastTarget;
+        InputTarget newTarget;
+        synchronized (mGlobalLock) {
+            lastTarget = getInputTargetFromToken(oldToken);
+            newTarget = getInputTargetFromToken(newToken);
+            if (newTarget == null && lastTarget == null) {
+                Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged");
+                return;
+            }
+
+            mAccessibilityController.onFocusChanged(lastTarget, newTarget);
+            ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget);
+        }
+
+        if (newTarget != null && newTarget.asWindowState() != null) {
+            WindowState newFocus = newTarget.asWindowState();
             mAnrController.onFocusChanged(newFocus);
             newFocus.reportFocusChangedSerialized(true);
             notifyFocusChanged();
         }
 
-        if (lastFocus != null) {
+        if (lastTarget != null && lastTarget.asWindowState() != null) {
+            WindowState lastFocus = lastTarget.asWindowState();
             lastFocus.reportFocusChangedSerialized(false);
         }
     }
@@ -7045,42 +7070,6 @@
         }
     }
 
-    private void checkCallerOwnsDisplay(int displayId) {
-        final Display display = mDisplayManager.getDisplay(displayId);
-        if (display == null) {
-            throw new IllegalArgumentException(
-                    "Cannot find display for non-existent displayId: " + displayId);
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        final int displayOwnerUid = display.getOwnerUid();
-        if (callingUid != displayOwnerUid) {
-            throw new SecurityException("The caller doesn't own the display.");
-        }
-    }
-
-    /** @see Session#updateDisplayContentLocation(IWindow, int, int, int)  */
-    void updateDisplayContentLocation(IWindow client, int x, int y, int displayId) {
-        checkCallerOwnsDisplay(displayId);
-
-        synchronized (mGlobalLock) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                final WindowState win = windowForClientLocked(null, client, false);
-                if (win == null) {
-                    ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
-                    return;
-                }
-                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-                if (displayContent != null) {
-                    displayContent.updateLocation(win, x, y);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
@@ -7159,29 +7148,6 @@
     }
 
     @Override
-    public void dontOverrideDisplayInfo(int displayId) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */);
-                if (dc == null) {
-                    throw new IllegalArgumentException(
-                            "Trying to configure a non existent display.");
-                }
-                // We usually set the override info in DisplayManager so that we get consistent
-                // values when displays are changing. However, we don't do this for displays that
-                // serve as containers for ActivityViews because we don't want letter-/pillar-boxing
-                // during resize.
-                dc.mShouldOverrideDisplayConfiguration = false;
-                mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(displayId,
-                        null /* info */);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
     public int getWindowingMode(int displayId) {
         if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "getWindowingMode()")) {
             throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
@@ -7522,11 +7488,7 @@
         @Override
         public IBinder getFocusedWindowToken() {
             synchronized (mGlobalLock) {
-                WindowState windowState = getFocusedWindowLocked();
-                if (windowState != null) {
-                    return windowState.mClient.asBinder();
-                }
-                return null;
+                return mAccessibilityController.getFocusedWindowToken();
             }
         }
 
@@ -8270,7 +8232,7 @@
             clientChannel = win.openInputChannel();
             mEmbeddedWindowController.add(clientChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
-            name = win.getName();
+            name = win.toString();
         }
 
         updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
@@ -8338,7 +8300,7 @@
                 Slog.e(TAG, "Couldn't find window for provided channelToken.");
                 return;
             }
-            name = win.getName();
+            name = win.toString();
             applicationHandle = win.getApplicationHandle();
         }
 
@@ -8547,10 +8509,9 @@
             SurfaceControl.Transaction t = mTransactionFactory.get();
             final int displayId = embeddedWindow.mDisplayId;
             if (grantFocus) {
-                t.setFocusedWindow(inputToken, embeddedWindow.getName(), displayId).apply();
+                t.setFocusedWindow(inputToken, embeddedWindow.toString(), displayId).apply();
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
-                        "Focus request " + embeddedWindow.getName(),
-                        "reason=grantEmbeddedWindowFocus(true)");
+                        "Focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)");
             } else {
                 // Search for a new focus target
                 DisplayContent displayContent = mRoot.getDisplayContent(displayId);
@@ -8559,18 +8520,18 @@
                 if (newFocusTarget == null) {
                     ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for "
                                     + "win=%s dropped since no candidate was found",
-                            embeddedWindow.getName());
+                            embeddedWindow);
                     return;
                 }
                 t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
-                        inputToken, embeddedWindow.getName(),
+                        inputToken, embeddedWindow.toString(),
                         displayId).apply();
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                         "Transfer focus request " + newFocusTarget,
                         "reason=grantEmbeddedWindowFocus(false)");
             }
             ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
-                    embeddedWindow.getName(), grantFocus);
+                    embeddedWindow, grantFocus);
         }
     }
 
@@ -8599,24 +8560,24 @@
             }
             SurfaceControl.Transaction t = mTransactionFactory.get();
             if (grantFocus) {
-                t.requestFocusTransfer(targetInputToken, embeddedWindow.getName(),
+                t.requestFocusTransfer(targetInputToken, embeddedWindow.toString(),
                         hostWindow.mInputChannel.getToken(),
                         hostWindow.getName(),
                         hostWindow.getDisplayId()).apply();
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
-                        "Transfer focus request " + embeddedWindow.getName(),
+                        "Transfer focus request " + embeddedWindow,
                         "reason=grantEmbeddedWindowFocus(true)");
             } else {
                 t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(),
                         targetInputToken,
-                        embeddedWindow.getName(),
+                        embeddedWindow.toString(),
                         hostWindow.getDisplayId()).apply();
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                         "Transfer focus request " + hostWindow,
                         "reason=grantEmbeddedWindowFocus(false)");
             }
             ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
-                    embeddedWindow.getName(), grantFocus);
+                    embeddedWindow, grantFocus);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f966cc1..a3d1378 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -216,7 +216,6 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.util.Slog;
@@ -273,7 +272,7 @@
 
 /** A window in the window manager. */
 class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
-        InsetsControlTarget {
+        InsetsControlTarget, InputTarget {
     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
 
     // The minimal size of a window within the usable area of the freeform root task.
@@ -405,9 +404,6 @@
 
     int mLayoutSeq = -1;
 
-    /** @see #addEmbeddedDisplayContent(DisplayContent dc) */
-    private final ArraySet<DisplayContent> mEmbeddedDisplayContents = new ArraySet<>();
-
     /**
      * Used to store last reported to client configuration and check if we have newer available.
      * We'll send configuration to client only if it is different from the last applied one and
@@ -669,12 +665,6 @@
     private final Transaction mTmpTransaction;
 
     /**
-     * If a window is on a display which has been re-parented to a view in another window,
-     * use this offset to indicate the correct location.
-     */
-    private final Point mLastReportedDisplayOffset = new Point();
-
-    /**
      * Whether the window was resized by us while it was gone for layout.
      */
     boolean mResizedWhileGone = false;
@@ -1727,7 +1717,8 @@
         return state;
     }
 
-    int getDisplayId() {
+    @Override
+    public int getDisplayId() {
         final DisplayContent displayContent = getDisplayContent();
         if (displayContent == null) {
             return Display.INVALID_DISPLAY;
@@ -1735,6 +1726,21 @@
         return displayContent.getDisplayId();
     }
 
+    @Override
+    public WindowState asWindowState() {
+        return this;
+    }
+
+    @Override
+    public IWindow getIWindow() {
+        return mClient;
+    }
+
+    @Override
+    public int getPid() {
+        return mSession.mPid;
+    }
+
     Task getTask() {
         return mActivityRecord != null ? mActivityRecord.getTask() : null;
     }
@@ -2290,7 +2296,6 @@
         if (mWmService.mAccessibilityController.hasCallbacks()) {
             mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
         }
-        updateLocationInParentDisplayIfNeeded();
 
         try {
             mClient.moved(left, top);
@@ -2637,29 +2642,7 @@
         mHasSurface = hasSurface;
     }
 
-    /**
-     * Checks whether one of the Windows in a Display embedded in this Window can be an IME target.
-     */
-    private boolean canWindowInEmbeddedDisplayBeImeTarget() {
-        final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size();
-        for (int i = embeddedDisplayContentsSize - 1; i >= 0; i--) {
-            final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
-            if (edc.forAllWindows(WindowState::canBeImeTarget, true)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean canBeImeTarget() {
-        // If any of the embedded windows can be the IME target, this window will be the final IME
-        // target. This is because embedded windows are on a different display in WM so it would
-        // cause confusion trying to set the IME to a window on a different display. Instead, just
-        // make the host window the IME target.
-        if (canWindowInEmbeddedDisplayBeImeTarget()) {
-            return true;
-        }
-
         if (mIsImWindow) {
             // IME windows can't be IME targets. IME targets are required to be below the IME
             // windows and that wouldn't be possible if the IME window is its own target...silly.
@@ -3945,7 +3928,6 @@
             if (mWmService.mAccessibilityController.hasCallbacks()) {
                 mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
             }
-            updateLocationInParentDisplayIfNeeded();
         } catch (RemoteException e) {
             // Cancel orientation change of this window to avoid blocking unfreeze display.
             setOrientationChanging(false);
@@ -3960,36 +3942,6 @@
         return mClient instanceof IWindow.Stub;
     }
 
-    void updateLocationInParentDisplayIfNeeded() {
-        final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size();
-        // If there is any embedded display which is re-parented to this window, we need to
-        // notify all windows in the embedded display about the location change.
-        if (embeddedDisplayContentsSize != 0) {
-            for (int i = embeddedDisplayContentsSize - 1; i >= 0; i--) {
-                final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
-                edc.notifyLocationInParentDisplayChanged();
-            }
-        }
-        // If this window is in a embedded display which is re-parented to another window,
-        // we may need to update its correct on-screen location.
-        final DisplayContent dc = getDisplayContent();
-        if (dc.getParentWindow() == null) {
-            return;
-        }
-
-        final Point offset = dc.getLocationInParentDisplay();
-        if (mLastReportedDisplayOffset.equals(offset)) {
-            return;
-        }
-
-        mLastReportedDisplayOffset.set(offset.x, offset.y);
-        try {
-            mClient.locationInParentDisplayChanged(mLastReportedDisplayOffset);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to update offset from DisplayContent", e);
-        }
-    }
-
     /**
      * Called when the insets state changed.
      */
@@ -4423,9 +4375,6 @@
         }
         pw.println(prefix + "isOnScreen=" + isOnScreen());
         pw.println(prefix + "isVisible=" + isVisible());
-        if (!mEmbeddedDisplayContents.isEmpty()) {
-            pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents);
-        }
         if (dumpAll) {
             final String visibilityString = mRequestedVisibilities.toString();
             if (!visibilityString.isEmpty()) {
@@ -5251,28 +5200,6 @@
         return mLayoutSeq != -1;
     }
 
-    /**
-     * Add the DisplayContent of the embedded display which is re-parented to this window to
-     * the list of embedded displays.
-     *
-     * @param dc DisplayContent of the re-parented embedded display.
-     * @return {@code true} if the giving DisplayContent is added, {@code false} otherwise.
-     */
-    boolean addEmbeddedDisplayContent(DisplayContent dc) {
-        return mEmbeddedDisplayContents.add(dc);
-    }
-
-    /**
-     * Remove the DisplayContent of the embedded display which is re-parented to this window from
-     * the list of embedded displays.
-     *
-     * @param dc DisplayContent of the re-parented embedded display.
-     * @return {@code true} if the giving DisplayContent is removed, {@code false} otherwise.
-     */
-    boolean removeEmbeddedDisplayContent(DisplayContent dc) {
-        return mEmbeddedDisplayContents.remove(dc);
-    }
-
     /** Updates the last frames and relative frames to the current ones. */
     void updateLastFrames() {
         mWindowFrames.mLastFrame.set(mWindowFrames.mFrame);
@@ -5358,18 +5285,6 @@
         int x = mSurfacePosition.x + mTmpPoint.x;
         int y = mSurfacePosition.y + mTmpPoint.y;
 
-        // We might be on a display which has been re-parented to a view in another window, so here
-        // computes the global location of our display.
-        DisplayContent dc = getDisplayContent();
-        while (dc != null && dc.getParentWindow() != null) {
-            final WindowState displayParent = dc.getParentWindow();
-            x += displayParent.mWindowFrames.mFrame.left
-                    + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
-            y += displayParent.mWindowFrames.mFrame.top
-                    + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
-            dc = displayParent.getDisplayContent();
-        }
-
         // If changed, also adjust transformFrameToSurfacePosition
         final WindowContainer parent = getParent();
         if (isChildWindow()) {
@@ -5681,7 +5596,7 @@
         // elevating the IME and windows above it's target above the docked divider in
         // split-screen, or make the popupMenu to be above the IME when the parent window is the
         // IME layering target in bubble/freeform mode.
-        if (mDisplayContent.isImeAttachedToApp()) {
+        if (mDisplayContent.shouldImeAttachedToApp()) {
             return false;
         }
 
@@ -5706,8 +5621,8 @@
     }
 
     /**
-     * Get IME target that should host IME when this window's display has a parent.
-     * Note: IME is never hosted by a display that has a parent.
+     * Get IME target that should host IME.
+     * Note: IME is never hosted by a display that doesn't support IME/system decorations.
      * When window calling
      * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} is unknown,
      * use {@link DisplayContent#getImeControlTarget()} instead.
@@ -5716,11 +5631,7 @@
      *         When window is doesn't have a parent, it is returned as-is.
      */
     InsetsControlTarget getImeControlTarget() {
-        final DisplayContent dc = getDisplayContent();
-        final WindowState parentWindow = dc.getParentWindow();
-
-        // If target's display has a parent, IME is displayed in the parent display.
-        return dc.getImeHostOrFallback(parentWindow != null ? parentWindow : this);
+        return getDisplayContent().getImeHostOrFallback(this);
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 59f831e..0447409 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10683,7 +10683,10 @@
             }
         }
 
-        if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile()) return;
+        if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile()
+                || user.isGuest()) {
+            return;
+        }
 
         if (mInjector.userManagerIsHeadlessSystemUserMode()) {
             ComponentName admin = mOwners.getDeviceOwnerComponent();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dae1275..fc60bab 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -133,6 +133,7 @@
 import com.android.server.inputmethod.InputMethodManagerService;
 import com.android.server.integrity.AppIntegrityManagerService;
 import com.android.server.lights.LightsService;
+import com.android.server.locales.LocaleManagerService;
 import com.android.server.location.LocationManagerService;
 import com.android.server.media.MediaRouterService;
 import com.android.server.media.metrics.MediaMetricsManagerService;
@@ -1684,6 +1685,15 @@
         mSystemServiceManager.startService(UiModeManagerService.class);
         t.traceEnd();
 
+        t.traceBegin("StartLocaleManagerService");
+        try {
+            mSystemServiceManager.startService(LocaleManagerService.class);
+        } catch (Throwable e) {
+            reportWtf("starting LocaleManagerService service", e);
+        }
+        t.traceEnd();
+
+
         if (!mOnlyCore) {
             t.traceBegin("UpdatePackagesIfNeeded");
             try {
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
new file mode 100644
index 0000000..a4de08a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="true" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
new file mode 100644
index 0000000..47649d7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
new file mode 100644
index 0000000..4fd9ebf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="false" />
+        <individual-sensor-privacy sensor="2" enabled="true" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
new file mode 100644
index 0000000..e8f9edf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="false" />
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+</sensor-privacy>
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 4738159..300f93f 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
@@ -178,7 +178,7 @@
             fail("registerUidObserver threw exception: " + e.getMessage());
         }
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
-        when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
         doReturn(mActivityMangerInternal)
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(AppStandbyInternal.class))
@@ -2128,7 +2128,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Test with timing sessions out of window but still under max execution limit.
@@ -2144,7 +2145,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false);
@@ -2152,7 +2154,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
         setStandbyBucket(standbyBucket, jobStatus);
@@ -2169,8 +2172,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
-                any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
@@ -2187,7 +2190,8 @@
             // No sessions saved yet.
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2196,7 +2200,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -2208,7 +2213,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2218,7 +2224,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2226,15 +2233,15 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
@@ -2251,7 +2258,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2260,7 +2268,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2270,7 +2279,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2280,7 +2290,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2288,15 +2299,15 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /**
@@ -2319,7 +2330,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2329,7 +2341,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2340,7 +2353,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2351,7 +2365,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2360,16 +2375,16 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
@@ -2389,7 +2404,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2398,7 +2414,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2412,7 +2429,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2422,7 +2440,8 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -2430,15 +2449,15 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -2471,9 +2490,10 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
-        inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .cancel(any(AlarmManager.OnAlarmListener.class));
 
         // And down from there.
         final long expectedWorkingAlarmTime =
@@ -2482,8 +2502,9 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedFrequentAlarmTime =
                 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
@@ -2491,8 +2512,9 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedRareAlarmTime =
                 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
@@ -2500,28 +2522,31 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // And back up again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
-        inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
     @Test
@@ -2547,7 +2572,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Valid time in the future, so the count should be used.
         stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
@@ -2556,8 +2582,9 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /**
@@ -2647,8 +2674,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
 
@@ -2680,8 +2707,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
@@ -3828,7 +3855,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
         for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -3846,7 +3874,8 @@
             advanceElapsedClock(SECOND_IN_MILLIS);
         }
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // The app is now out of job count quota
         JobStatus throttledJob = createJobStatus(
@@ -3863,8 +3892,9 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /**
@@ -3894,7 +3924,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
         for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -3914,7 +3945,8 @@
             advanceElapsedClock(SECOND_IN_MILLIS);
         }
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // The app is now out of session count quota
         JobStatus throttledJob = createJobStatus(
@@ -3932,8 +3964,9 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
@@ -4391,8 +4424,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4402,8 +4435,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -4414,8 +4447,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -4426,8 +4459,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -4436,16 +4469,16 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -4483,9 +4516,10 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
-        inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .cancel(any(AlarmManager.OnAlarmListener.class));
 
         // And down from there.
         setStandbyBucket(WORKING_INDEX);
@@ -4496,8 +4530,9 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         setStandbyBucket(FREQUENT_INDEX);
         final long expectedFrequentAlarmTime =
@@ -4506,8 +4541,9 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         setStandbyBucket(RARE_INDEX);
         final long expectedRareAlarmTime =
@@ -4517,8 +4553,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // And back up again.
         setStandbyBucket(FREQUENT_INDEX);
@@ -4526,25 +4562,28 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         setStandbyBucket(WORKING_INDEX);
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
-        inOrder.verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                eq(TAG_QUOTA_CHECK), any(), any());
 
         setStandbyBucket(ACTIVE_INDEX);
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
-        inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
-        inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .cancel(any(AlarmManager.OnAlarmListener.class));
     }
 
     /**
@@ -4578,8 +4617,8 @@
             mQuotaController.maybeScheduleStartAlarmLocked(
                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /** Tests that TimingSessions aren't saved when the device is charging. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 63996f0..4d6f49e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,7 +90,7 @@
     }
 
     @Test
-    public void testThrottle() {
+    public void testThrottle_stationaryExit() {
         ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
 
         mProvider.getController().setRequest(request);
@@ -113,6 +113,29 @@
     }
 
     @Test
+    public void testThrottle_idleExit() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceIdleHelper().setIdle(false);
+        verify(mDelegate, times(2)).onSetRequest(request);
+        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
     public void testThrottle_NoInitialLocation() {
         ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index ba79a76..38f01b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,12 +16,18 @@
 
 package com.android.server.sensorprivacy;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
+import android.app.AppOpsManagerInternal;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Environment;
@@ -33,8 +39,10 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.LocalServices;
 import com.android.server.SensorPrivacyService;
+import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -44,6 +52,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
 
 @RunWith(AndroidTestingRunner.class)
 public class SensorPrivacyServiceMockingTest {
@@ -63,10 +72,21 @@
     public static final String PERSISTENCE_FILE6 =
             String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
 
+    public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
+            "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
+    public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
+            "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
+    public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
+            "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
+    public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
+            "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
+
     private Context mContext;
     @Mock
     private AppOpsManager mMockedAppOpsManager;
     @Mock
+    private AppOpsManagerInternal mMockedAppOpsManagerInternal;
+    @Mock
     private UserManagerInternal mMockedUserManagerInternal;
     @Mock
     private ActivityManager mMockedActivityManager;
@@ -134,13 +154,103 @@
         }
     }
 
+    @Test
+    public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
+        testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+    }
+
+    @Test
+    public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
+        testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+    }
+
+    @Test
+    public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
+        testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
+    }
+
+    @Test
+    public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
+        testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
+    }
+
+    private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
+            boolean expectedMicState, boolean expectedCamState)
+            throws IOException {
+        MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .spyStatic(LocalServices.class)
+                .spyStatic(Environment.class)
+                .startMocking();
+
+        try {
+            mContext = InstrumentationRegistry.getInstrumentation().getContext();
+            spyOn(mContext);
+
+            doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+            doReturn(mMockedAppOpsManagerInternal)
+                    .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
+            doReturn(mMockedUserManagerInternal)
+                    .when(() -> LocalServices.getService(UserManagerInternal.class));
+            doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+            doReturn(mMockedActivityTaskManager)
+                    .when(mContext).getSystemService(ActivityTaskManager.class);
+            doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+                    TelephonyManager.class);
+
+            String dataDir = mContext.getApplicationInfo().dataDir;
+            doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+            File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+            onDeviceFile.delete();
+
+            doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+            doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+                    .getUserInfo(0);
+
+            CompletableFuture<Boolean> micState = new CompletableFuture<>();
+            CompletableFuture<Boolean> camState = new CompletableFuture<>();
+            doAnswer(invocation -> {
+                int code = invocation.getArgument(0);
+                boolean restricted = invocation.getArgument(1);
+                if (code == AppOpsManager.OP_RECORD_AUDIO) {
+                    micState.complete(restricted);
+                } else if (code == AppOpsManager.OP_CAMERA) {
+                    camState.complete(restricted);
+                }
+                return null;
+            }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
+                    any());
+
+            initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
+
+            Assert.assertTrue(micState.join() == expectedMicState);
+            Assert.assertTrue(camState.join() == expectedCamState);
+
+        } finally {
+            mockitoSession.finishMocking();
+        }
+    }
+
     private void initServiceWithPersistenceFile(File onDeviceFile,
             String persistenceFilePath) throws IOException {
+        initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+    }
+
+    private void initServiceWithPersistenceFile(File onDeviceFile,
+            String persistenceFilePath, int startingUserId) throws IOException {
         if (persistenceFilePath != null) {
             Files.copy(mContext.getAssets().open(persistenceFilePath),
                     onDeviceFile.toPath());
         }
-        new SensorPrivacyService(mContext);
+        SensorPrivacyService service = new SensorPrivacyService(mContext);
+        if (startingUserId != -1) {
+            SystemService.TargetUser mockedTargetUser =
+                    ExtendedMockito.mock(SystemService.TargetUser.class);
+            doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
+            service.onUserStarting(mockedTargetUser);
+        }
         onDeviceFile.delete();
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
new file mode 100644
index 0000000..849e673
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.utils;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link AlarmQueue}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AlarmQueueTest {
+    private static final String ALARM_TAG = "*test*";
+
+    private final InjectorForTest mInjector = new InjectorForTest();
+    private ArraySet<String> mExpiredPackages;
+    private MockitoSession mMockingSession;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private Context mContext;
+
+    private static class InjectorForTest extends AlarmQueue.Injector {
+        private long mElapsedTime = SystemClock.elapsedRealtime();
+
+        @Override
+        long getElapsedRealtime() {
+            return mElapsedTime;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+        // Freeze the clocks at 24 hours after this moment in time.
+        advanceElapsedClock(24 * HOUR_IN_MILLIS);
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        mInjector.mElapsedTime += incrementMs;
+    }
+
+    @NonNull
+    private AlarmQueue<String> createAlarmQueue(boolean exactAlarm, long minTimeBetweenAlarmsMs) {
+        return new AlarmQueue<String>(mContext, mContext.getMainLooper(), ALARM_TAG, "Test",
+                exactAlarm, minTimeBetweenAlarmsMs, mInjector) {
+            @Override
+            protected boolean isForUser(String key, int userId) {
+                return true;
+            }
+
+            @Override
+            protected void processExpiredAlarms(@NonNull ArraySet<String> expired) {
+                mExpiredPackages = expired;
+            }
+        };
+    }
+
+    @Test
+    public void testAddingIncreasingAlarms() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .setExact(anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm("com.android.test.2", nowElapsed + 2 * HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, never())
+                .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm("com.android.test.3", nowElapsed + 3 * HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, never())
+                .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
+    public void testAddingDecreasingAlarms() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        alarmQueue.addAlarm("com.android.test.3", nowElapsed + 3 * HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 3 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm("com.android.test.2", nowElapsed + 2 * HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 2 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    /**
+     * Verify that updating the alarm time for a key will result in the AlarmManager alarm changing,
+     * if needed.
+     */
+    @Test
+    public void testChangingKeyAlarm() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        alarmQueue.addAlarm("1", nowElapsed + 5 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 5 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        // Only alarm, but the time has changed, so we should reschedule what's set with AM.
+        alarmQueue.addAlarm("1", nowElapsed + 20 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 20 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        alarmQueue.addAlarm("1", nowElapsed + 10 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 10 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        // Add another keyed alarm and check that we don't bother rescheduling when the changed
+        // alarm is after the first alarm to go off.
+        alarmQueue.addAlarm("2", nowElapsed + 11 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, never()).setExact(
+                anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+
+        alarmQueue.addAlarm("2", nowElapsed + 51 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, never()).setExact(
+                anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+
+        alarmQueue.addAlarm("1", nowElapsed + 52 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 51 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
+    public void testInexactQueue() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(false, 0);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS);
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
+    public void testMinTimeBetweenAlarms() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 2 * HOUR_IN_MILLIS);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        final String pkg1 = "com.android.test.1";
+        final String pkg2 = "com.android.test.2";
+        final String pkg3 = "com.android.test.3";
+        alarmQueue.addAlarm(pkg1, nowElapsed + HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm(pkg2, nowElapsed + 2 * HOUR_IN_MILLIS);
+        alarmQueue.addAlarm(pkg3, nowElapsed + 3 * HOUR_IN_MILLIS);
+        alarmQueue.addAlarm("com.android.test.4", nowElapsed + 4 * HOUR_IN_MILLIS);
+
+        advanceElapsedClock(HOUR_IN_MILLIS);
+
+        alarmQueue.onAlarm();
+        // Minimum of 2 hours between alarms, so the next alarm should be 2 hours after the first.
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 3 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+        alarmQueue.onAlarm();
+        // Minimum of 2 hours between alarms, so the next alarm should be 2 hours after the second.
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 5 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
+    public void testOnAlarm() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
+        final long nowElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        final String pkg1 = "com.android.test.1";
+        final String pkg2 = "com.android.test.2";
+        final String pkg3 = "com.android.test.3";
+        final String pkg4 = "com.android.test.4";
+        alarmQueue.addAlarm(pkg1, nowElapsed + HOUR_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+        alarmQueue.addAlarm(pkg2, nowElapsed + 2 * HOUR_IN_MILLIS);
+        alarmQueue.addAlarm(pkg3, nowElapsed + 3 * HOUR_IN_MILLIS);
+        alarmQueue.addAlarm(pkg4, nowElapsed + 4 * HOUR_IN_MILLIS);
+
+        advanceElapsedClock(HOUR_IN_MILLIS);
+
+        final ArraySet<String> expectedExpired = new ArraySet<>();
+
+        expectedExpired.add(pkg1);
+        alarmQueue.onAlarm();
+        assertEquals(expectedExpired, mExpiredPackages);
+        // The next alarm should also be scheduled.
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 2 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(2 * HOUR_IN_MILLIS);
+
+        expectedExpired.clear();
+        expectedExpired.add(pkg2);
+        expectedExpired.add(pkg3);
+        alarmQueue.onAlarm();
+        assertEquals(expectedExpired, mExpiredPackages);
+        // The next alarm should also be scheduled.
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(nowElapsed + 4 * HOUR_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(HOUR_IN_MILLIS);
+
+        expectedExpired.clear();
+        expectedExpired.add(pkg4);
+        alarmQueue.onAlarm();
+        assertEquals(expectedExpired, mExpiredPackages);
+        // No more alarms, so nothing should be scheduled with AlarmManager.
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .setExact(anyInt(), anyLong(), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
+    public void testSettingMinTimeBetweenAlarms() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 50);
+        assertEquals(50, alarmQueue.getMinTimeBetweenAlarmsMs());
+
+        alarmQueue.setMinTimeBetweenAlarmsMs(2345);
+        assertEquals(2345, alarmQueue.getMinTimeBetweenAlarmsMs());
+
+        try {
+            alarmQueue.setMinTimeBetweenAlarmsMs(-1);
+            fail("Successfully set negative time between alarms");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+        try {
+            createAlarmQueue(false, -1);
+            fail("Successfully set negative time between alarms");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
index 80aec73..608b64e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -588,37 +588,41 @@
 
         // No sessions saved yet.
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, never()).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = mInjector.getElapsedRealtime();
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, never()).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS;
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, never()).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, never()).setWindow(
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
-        verify(mAlarmManager, times(1))
-                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+        verify(mAlarmManager, times(1)).setWindow(
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
@@ -652,7 +656,7 @@
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, never())
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
         inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
 
         // And down from there.
@@ -660,37 +664,42 @@
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                        eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                        eq(TAG_QUOTA_CHECK), any(), any());
 
         final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(),
+                        eq(TAG_QUOTA_CHECK), any(), any());
 
         // And back up again.
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
+                        eq(TAG_QUOTA_CHECK), any(), any());
 
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
-                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
+                        eq(TAG_QUOTA_CHECK), any(), any());
 
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index 4a67ec7..8a8a624 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -74,15 +74,20 @@
     @Test
     public void testTargetedEnergyConsumerQuerying() {
         final int numCpuClusters = 4;
+        final int numDisplays = 5;
         final int numOther = 3;
 
         // Add some energy consumers used by BatteryExternalStatsWorker.
         final IntArray tempAllIds = new IntArray();
 
-        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
-                "display");
-        tempAllIds.add(displayId);
-        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+        final int[] displayIds = new int[numDisplays];
+        for (int i = 0; i < numDisplays; i++) {
+            displayIds[i] = mPowerStatsInternal.addEnergyConsumer(
+                    EnergyConsumerType.DISPLAY, i, "display" + i);
+            tempAllIds.add(displayIds[i]);
+            mPowerStatsInternal.incrementEnergyConsumption(displayIds[i], 12345 + i);
+        }
+        Arrays.sort(displayIds);
 
         final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0,
                 "wifi");
@@ -130,9 +135,13 @@
 
         final EnergyConsumerResult[] displayResults =
                 mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
-        // Results should only have the display energy consumer
-        assertEquals(1, displayResults.length);
-        assertEquals(displayId, displayResults[0].id);
+        // Results should only have the cpu cluster energy consumers
+        final int[] receivedDisplayIds = new int[displayResults.length];
+        for (int i = 0; i < displayResults.length; i++) {
+            receivedDisplayIds[i] = displayResults[i].id;
+        }
+        Arrays.sort(receivedDisplayIds);
+        assertArrayEquals(displayIds, receivedDisplayIds);
 
         final EnergyConsumerResult[] wifiResults =
                 mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 8c87506..a0cbcad 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -120,7 +118,7 @@
         // results0
         MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
         if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
-            assertEquals(UNAVAILABLE, delta.displayChargeUC);
+            assertNull(delta.displayChargeUC);
             assertNull(delta.otherTotalChargeUC);
             assertNull(delta.otherUidChargesUC);
         }
@@ -130,7 +128,7 @@
         assertNotNull(delta);
         long expectedChargeUC;
         expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1);
-        assertEquals(expectedChargeUC, delta.displayChargeUC);
+        assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
 
         assertNotNull(delta.otherTotalChargeUC);
 
@@ -149,14 +147,14 @@
         delta = snapshot.updateAndGetDelta(RESULTS_2, VOLTAGE_2);
         assertNotNull(delta);
         expectedChargeUC = calculateChargeConsumedUC(24_000, VOLTAGE_1, 36_000, VOLTAGE_2);
-        assertEquals(expectedChargeUC, delta.displayChargeUC);
+        assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
         assertNull(delta.otherUidChargesUC);
         assertNull(delta.otherTotalChargeUC);
 
         // results3
         delta = snapshot.updateAndGetDelta(RESULTS_3, VOLTAGE_3);
         assertNotNull(delta);
-        assertEquals(UNAVAILABLE, delta.displayChargeUC);
+        assertNull(delta.displayChargeUC);
 
         assertNotNull(delta.otherTotalChargeUC);
 
@@ -183,7 +181,7 @@
         delta = snapshot.updateAndGetDelta(RESULTS_4, VOLTAGE_4);
         assertNotNull(delta);
         expectedChargeUC = calculateChargeConsumedUC(36_000, VOLTAGE_2, 43_000, VOLTAGE_4);
-        assertEquals(expectedChargeUC, delta.displayChargeUC);
+        assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
 
         assertNotNull(delta.otherTotalChargeUC);
         expectedChargeUC = calculateChargeConsumedUC(190_000, VOLTAGE_3, 290_000, VOLTAGE_4);
@@ -210,7 +208,7 @@
         // results0
         MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
         if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
-            assertEquals(UNAVAILABLE, delta.displayChargeUC);
+            assertNull(delta.displayChargeUC);
             assertNull(delta.otherTotalChargeUC);
             assertNull(delta.otherUidChargesUC);
         }
@@ -220,7 +218,7 @@
         assertNotNull(delta);
         final long expectedChargeUC =
                 calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1);
-        assertEquals(expectedChargeUC, delta.displayChargeUC);
+        assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
         assertNull(delta.otherTotalChargeUC); // Although in the results, they're not in the idMap
         assertNull(delta.otherUidChargesUC);
     }
diff --git a/services/tests/servicestests/src/com/android/server/criticalevents/OWNERS b/services/tests/servicestests/src/com/android/server/criticalevents/OWNERS
new file mode 100644
index 0000000..07c0e70
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/criticalevents/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/criticalevents/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index ac2756b..cf4bdf6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -28,6 +28,7 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -42,6 +43,7 @@
 
 /** Tests for {@link ActiveSourceAction} */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class ActiveSourceActionTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 29eb0da..638b386 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -34,6 +34,7 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -50,6 +51,7 @@
 
 /** Tests for {@link DevicePowerStatusAction} */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class DevicePowerStatusActionTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 8137d12..41231e0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -36,6 +36,7 @@
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -52,6 +53,7 @@
 import java.util.Collections;
 
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class DeviceSelectActionFromPlaybackTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 28ee592..dd74864 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -36,6 +36,7 @@
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
 import java.util.Collections;
 
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class DeviceSelectActionFromTvTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index bd6e46d..2dcc449 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -85,6 +85,7 @@
     private HdmiCecController mHdmiCecController;
     private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B;
     private int mLogicalAddress = 16;
+    private int mPlaybackLogicalAddress;
     private AllocateAddressCallback mCallback =
             new AllocateAddressCallback() {
                 @Override
@@ -121,7 +122,6 @@
         HdmiCecLocalDevicePlayback playbackDevice =
                 new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy);
         playbackDevice.init();
-
         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
         localDevices.add(playbackDevice);
 
@@ -129,7 +129,11 @@
         mHdmiControlServiceSpy.allocateLogicalAddress(localDevices,
                 HdmiControlService.INITIATED_BY_ENABLE_CEC);
         mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mTestLooper.dispatchAll();
 
+        synchronized (playbackDevice.mLock) {
+            mPlaybackLogicalAddress = playbackDevice.getDeviceInfo().getLogicalAddress();
+        }
         mTestLooper.dispatchAll();
     }
 
@@ -374,7 +378,7 @@
         doReturn(HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
 
         HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
-                ADDR_TV, ADDR_PLAYBACK_1);
+                ADDR_TV, mPlaybackLogicalAddress);
         mNativeWrapper.onCecMessage(receivedMessage);
 
         mTestLooper.dispatchAll();
@@ -391,13 +395,13 @@
         doReturn(NOT_HANDLED).when(mHdmiControlServiceSpy).handleCecCommand(any());
 
         HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
-                ADDR_TV, ADDR_PLAYBACK_1);
+                ADDR_TV, mPlaybackLogicalAddress);
         mNativeWrapper.onCecMessage(receivedMessage);
 
         mTestLooper.dispatchAll();
 
         HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE);
+                mPlaybackLogicalAddress, DEVICE_TV, MESSAGE_STANDBY, ABORT_UNRECOGNIZED_OPCODE);
         assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
     }
 
@@ -408,13 +412,13 @@
         doReturn(ABORT_REFUSED).when(mHdmiControlServiceSpy).handleCecCommand(any());
 
         HdmiCecMessage receivedMessage = HdmiCecMessageBuilder.buildStandby(
-                ADDR_TV, ADDR_PLAYBACK_1);
+                ADDR_TV, mPlaybackLogicalAddress);
         mNativeWrapper.onCecMessage(receivedMessage);
 
         mTestLooper.dispatchAll();
 
         HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
-                DEVICE_PLAYBACK, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED);
+                mPlaybackLogicalAddress, DEVICE_TV, MESSAGE_STANDBY, ABORT_REFUSED);
         assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 372fd60..a260a6d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -39,6 +39,7 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -55,6 +56,7 @@
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 /** Tests for {@link HdmiCecLocalDeviceTv} class. */
 public class HdmiCecLocalDeviceTvTest {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
index 036d084..cca5094 100755
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.hdmi;
 
+import android.platform.test.annotations.Presubmit;
+
 import androidx.test.filters.SmallTest;
 
 import com.google.common.testing.EqualsTester;
@@ -25,6 +27,7 @@
 
 /** Tests for {@link HdmiCecMessage} class. */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class HdmiCecMessageTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 5074f64..22ad956 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -29,9 +29,11 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -47,6 +49,7 @@
 
 /** Tests for {@link OneTouchPlayAction} */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class OneTouchPlayActionTest {
     private static final byte[] POWER_ON = new byte[]{HdmiControlManager.POWER_STATUS_ON};
@@ -180,13 +183,16 @@
     public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception {
         setUp(true);
 
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
@@ -230,13 +236,16 @@
     public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception {
         setUp(true);
 
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
@@ -293,13 +302,16 @@
     public void timeOut_Cec14b() throws Exception {
         setUp(true);
 
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
@@ -339,13 +351,15 @@
     @Test
     public void succeedIfPowerStatusOn_Cec20() throws Exception {
         setUp(true);
-
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_ON);
         mTestLooper.dispatchAll();
@@ -377,13 +391,15 @@
     @Test
     public void succeedIfPowerStatusUnknown_Cec20() throws Exception {
         setUp(true);
-
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_UNKNOWN);
         mTestLooper.dispatchAll();
@@ -429,13 +445,15 @@
     @Test
     public void succeedIfPowerStatusStandby_Cec20() throws Exception {
         setUp(true);
-
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
         mLocalDevices.add(playbackDevice);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_TV,
                 HdmiControlManager.POWER_STATUS_STANDBY);
         mTestLooper.dispatchAll();
@@ -565,7 +583,6 @@
     public void pendingActionDoesNotBlockSendingStandby_Cec14b() throws Exception {
         setUp(true);
 
-        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
         HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
                 mHdmiControlService);
         playbackDevice.init();
@@ -575,6 +592,10 @@
                 HdmiControlManager.POWER_CONTROL_MODE_TV);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
+
+        mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+        mTestLooper.dispatchAll();
         mNativeWrapper.clearResultMessages();
 
         TestActionTimer actionTimer = new TestActionTimer();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 2b49095..2f22bce 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -33,6 +33,7 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -48,6 +49,7 @@
 
 /** Tests for {@link PowerStatusMonitorAction} */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class PowerStatusMonitorActionTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 9589b73..823ed2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -41,6 +42,7 @@
 import java.util.List;
 
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class RequestSadActionTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 0916b12..302c0ac 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -35,6 +35,7 @@
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
 import java.util.List;
 
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class RoutingControlActionTest {
     /*
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 949cf9f..b34b853 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -31,6 +31,7 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -47,6 +48,7 @@
  * Test for {@link SystemAudioAutoInitiationAction}.
  */
 @SmallTest
+@Presubmit
 @RunWith(JUnit4.class)
 public class SystemAudioAutoInitiationActionTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
new file mode 100644
index 0000000..1613b11
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
@@ -0,0 +1,57 @@
+/*
+ * 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.locales;
+
+import android.annotation.Nullable;
+import android.os.LocaleList;
+
+import com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater;
+
+/**
+ * Test double for the {@link PackageConfigurationUpdater}. For use in
+ * {@link LocaleManagerServiceTest}s to stub out storage and check for state-based changes.
+ */
+class FakePackageConfigurationUpdater implements PackageConfigurationUpdater {
+
+    FakePackageConfigurationUpdater() {}
+
+    LocaleList mLocales = null;
+
+    @Override
+    public PackageConfigurationUpdater setNightMode(int nightMode) {
+        return this;
+    }
+
+    @Override
+    public PackageConfigurationUpdater setLocales(LocaleList locales) {
+        mLocales = locales;
+        return this;
+    }
+
+    @Override
+    public void commit() {}
+
+    /**
+     * Returns the locales that were stored during the test run. Returns {@code null} if no locales
+     * were set.
+     */
+    @Nullable
+    LocaleList getStoredLocales() {
+        return mLocales;
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
new file mode 100644
index 0000000..1141d9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.locales;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.LocaleList;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal.PackageConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for the {@link LocaleManagerService}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleManagerServiceTest {
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
+    private static final int DEFAULT_USER_ID = 0;
+    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final int INVALID_UID = -1;
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC, ar-XB";
+    private static final LocaleList DEFAULT_LOCALES =
+            LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
+
+    private LocaleManagerService mLocaleManagerService;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManagerInternal mMockPackageManager;
+    @Mock
+    private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
+    @Mock
+    private ActivityTaskManagerInternal mMockActivityTaskManager;
+    @Mock
+    private ActivityManagerInternal mMockActivityManager;
+
+    @Before
+    public void setUp() {
+        mMockContext = mock(Context.class);
+        mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
+        mMockActivityManager = mock(ActivityManagerInternal.class);
+        mMockPackageManager = mock(PackageManagerInternal.class);
+
+        mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
+        doReturn(mFakePackageConfigurationUpdater)
+                .when(mMockActivityTaskManager)
+                .createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        doReturn(mFakePackageConfigurationUpdater)
+                .when(mMockActivityTaskManager).createPackageConfigurationUpdater();
+
+        doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
+                .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
+                        anyString(), anyString());
+
+        mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
+                mMockActivityManager, mMockPackageManager);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
+        doReturn(DEFAULT_UID)
+                .when(mMockPackageManager).getPackageUid(anyString(), anyInt(), anyInt());
+        setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
+
+        try {
+            mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
+                    LocaleList.getEmptyLocaleList());
+            fail("Expected SecurityException");
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.CHANGE_CONFIGURATION),
+                    anyString());
+            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
+        try {
+            mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
+                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
+            fail("Expected NullPointerException");
+        } finally {
+            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetApplicationLocales_nullLocaleList_fails() throws Exception {
+        setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
+
+        try {
+            mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
+                    /* locales = */ null);
+            fail("Expected NullPointerException");
+        } finally {
+            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+        }
+    }
+
+
+    @Test
+    public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
+        doReturn(DEFAULT_UID)
+                .when(mMockPackageManager).getPackageUid(anyString(), anyInt(), anyInt());
+        // if package is not owned by the caller, the calling app should have the following
+        //   permission. We will mock this to succeed to imitate that.
+        setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
+
+        mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
+                DEFAULT_LOCALES);
+
+        assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+
+    }
+
+    @Test
+    public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
+        doReturn(Binder.getCallingUid())
+                .when(mMockPackageManager).getPackageUid(anyString(), anyInt(), anyInt());
+
+        mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
+                DEFAULT_LOCALES);
+
+        assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
+        doReturn(INVALID_UID)
+                .when(mMockPackageManager).getPackageUid(anyString(), anyInt(), anyInt());
+        try {
+            mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
+                    LocaleList.getEmptyLocaleList());
+            fail("Expected IllegalArgumentException");
+        } finally {
+            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+        }
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUid(anyString(), anyInt(), anyInt());
+        setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+        try {
+            mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+            fail("Expected SecurityException");
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
+            throws Exception {
+        // any valid app calling for its own package or having appropriate permission
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUid(anyString(), anyInt(), anyInt());
+        setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+        doReturn(null)
+                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+
+        LocaleList locales = mLocaleManagerService.getApplicationLocales(
+                    DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.getEmptyLocaleList(), locales);
+    }
+
+    @Test
+    public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
+            throws Exception {
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUid(anyString(), anyInt(), anyInt());
+        setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+        doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
+                .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
+
+        LocaleList locales = mLocaleManagerService.getApplicationLocales(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.getEmptyLocaleList(), locales);
+    }
+
+    @Test
+    public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
+            throws Exception {
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUid(anyString(), anyInt(), anyInt());
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+
+        LocaleList locales =
+                mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(DEFAULT_LOCALES, locales);
+    }
+
+    @Test
+    public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
+            throws Exception {
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUid(anyString(), anyInt(), anyInt());
+        setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+
+        LocaleList locales =
+                mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(DEFAULT_LOCALES, locales);
+    }
+
+    private static void assertNoLocalesStored(LocaleList locales) {
+        assertNull(locales);
+    }
+
+    private void setUpFailingPermissionCheckFor(String permission) {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(eq(permission), any());
+    }
+
+    private void setUpPassingPermissionCheckFor(String permission) {
+        doNothing().when(mMockContext).enforceCallingPermission(eq(permission), any());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/OWNERS b/services/tests/servicestests/src/com/android/server/locales/OWNERS
new file mode 100644
index 0000000..eecb6ea
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/locales/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
index acd3fca..3261dfa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
@@ -125,7 +125,7 @@
     public void checkDeviceIdentifierAccess_hasPrivilegedPermission_returnsGranted() {
         // Apps with the READ_PRIVILEGED_PHONE_STATE permission should have access to device
         // identifiers.
-        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
         when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
 
@@ -140,7 +140,7 @@
     public void checkDeviceIdentifierAccess_hasAppOp_returnsGranted() {
         // Apps that have been granted the READ_DEVICE_IDENTIFIERS appop should have access to
         // device identifiers.
-        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
         when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS),
                 eq(APP_UID), eq(mPackageName), any(), any())).thenReturn(
                 AppOpsManager.MODE_ALLOWED);
@@ -156,7 +156,7 @@
     public void checkDeviceIdentifierAccess_hasDpmAccess_returnsGranted() {
         // Apps that pass a DevicePolicyManager device / profile owner check should have access to
         // device identifiers.
-        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+        setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
         when(mDevicePolicyManager.hasDeviceIdentifierAccess(mPackageName, APP_PID,
                 APP_UID)).thenReturn(true);
 
@@ -236,7 +236,7 @@
         // both the permission and the appop must be granted. If the permission is granted but the
         // appop is not then AppOpsManager#MODE_IGNORED should be returned to indicate that this
         // should be a silent failure.
-        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
         setPackageTargetSdk(Build.VERSION_CODES.Q);
 
         grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
@@ -256,7 +256,7 @@
         // Apps targeting R+ with just the READ_PHONE_STATE permission granted should not have
         // access to the phone number; PERMISSION_DENIED should be returned both with and without
         // the appop granted since this check should be skipped for target SDK R+.
-        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
 
         grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
         int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(
@@ -319,12 +319,79 @@
         assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop);
     }
 
+    @Test
+    public void checkPhoneNumberAccess_providedUidDoesNotMatchPackageUid_throwsException()
+            throws Exception {
+        // An app can directly interact with one of the services that accepts a package name and
+        // returns a protected resource via a direct binder transact. This app could then provide
+        // the name of another app that targets pre-R, then determine if the app is installed based
+        // on whether the service throws an exception or not. While the app can provide the package
+        // name of another app, it cannot specify the package uid which is passed to the
+        // LegacyPermissionManager using Binder#getCallingUid. Ultimately this uid should then be
+        // compared against the actual uid of the package to ensure information about packages
+        // installed on the device is not leaked.
+        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID + 1);
+
+        assertThrows(SecurityException.class,
+                () -> mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+                        CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID));
+    }
+
+    @Test
+    public void checkPhoneNumberAccess_nullPackageNameSystemUid_returnsGranted() throws Exception {
+        // The platform can pass a null package name when checking if the platform itself has
+        // access to the device phone number(s) / identifier(s). This test ensures if a null package
+        // is provided, then the package uid check is skipped and the test is based on whether the
+        // the provided uid / pid has been granted the privileged permission.
+        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+        when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                SYSTEM_PID, SYSTEM_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(null,
+                CHECK_PHONE_NUMBER_MESSAGE, null, SYSTEM_PID, SYSTEM_UID);
+
+        assertEquals(PackageManager.PERMISSION_GRANTED, result);
+    }
+
+    @Test
+    public void checkPhoneNumberAccess_systemUidMismatchPackageUid_returnsGranted()
+            throws Exception {
+        // When the platform is checking device phone number / identifier access checks for other
+        // components on the platform, a uid less than the first application UID is provided; this
+        // test verifies the package uid check is skipped and access is still granted with the
+        // privileged permission.
+        int telephonyUid = SYSTEM_UID + 1;
+        int telephonyPid = SYSTEM_PID + 1;
+        setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+        when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                telephonyPid, telephonyUid)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+                CHECK_PHONE_NUMBER_MESSAGE, null, telephonyPid, telephonyUid);
+
+        assertEquals(PackageManager.PERMISSION_GRANTED, result);
+    }
+
     /**
      * Configures device identifier access tests to fail; tests verifying access should individually
      * set an access check to succeed to verify access when that condition is met.
      */
     private void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid) {
-        setupAccessTest(callingPid, callingUid);
+        setupCheckDeviceIdentifierAccessTest(callingPid, callingUid, callingUid);
+    }
+
+    /**
+     * Configures device identifier access tests to fail; tests verifying access should individually
+     * set an access check to succeed to verify access when that condition is met.
+     *
+     * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+     * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+     * the package being checked; to ensure this check is successful, this method accepts the
+     * {@code packageUid} to be used for the package being checked.
+     */
+    public void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid,
+            int packageUid) {
+        setupAccessTest(callingPid, callingUid, packageUid);
 
         when(mDevicePolicyManager.hasDeviceIdentifierAccess(anyString(), anyInt(),
                 anyInt())).thenReturn(false);
@@ -333,11 +400,26 @@
     }
 
     /**
-     * Configures phone number access tests to fail; tests verifying access should individually set
-     * an access check to succeed to verify access when that condition is met.
+     * Configures phone number access tests to fail; tests verifying access should individually
+     * set an access check to succeed to verify access when that condition is set.
+     *
      */
     private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception {
-        setupAccessTest(callingPid, callingUid);
+        setupCheckPhoneNumberAccessTest(callingPid, callingUid, callingUid);
+    }
+
+    /**
+     * Configures phone number access tests to fail; tests verifying access should individually set
+     * an access check to succeed to verify access when that condition is met.
+     *
+     * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+     * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+     * the package being checked; to ensure this check is successful, this method accepts the
+     * {@code packageUid} to be used for the package being checked.
+     */
+    private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid, int packageUid)
+            throws Exception {
+        setupAccessTest(callingPid, callingUid, packageUid);
         setPackageTargetSdk(Build.VERSION_CODES.R);
     }
 
@@ -345,9 +427,10 @@
      * Configures the common mocks for any access tests using the provided {@code callingPid}
      * and {@code callingUid}.
      */
-    private void setupAccessTest(int callingPid, int callingUid) {
+    private void setupAccessTest(int callingPid, int callingUid, int packageUid) {
         when(mInjector.getCallingPid()).thenReturn(callingPid);
         when(mInjector.getCallingUid()).thenReturn(callingUid);
+        when(mInjector.getPackageUidForUser(anyString(), anyInt())).thenReturn(packageUid);
 
         when(mInjector.checkPermission(anyString(), anyInt(), anyInt())).thenReturn(
                 PackageManager.PERMISSION_DENIED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index a3ad09a..c103bc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -42,6 +43,7 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.testing.DexmakerShareClassLoaderRule;
+import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
@@ -64,7 +66,7 @@
  * Unit tests for {@link ActivityStartInterceptorTest}.
  *
  * Build/Install/Run:
- *  atest WmTests:ActivityStartInterceptorTest
+ * atest WmTests:ActivityStartInterceptorTest
  */
 @SmallTest
 @Presubmit
@@ -114,6 +116,9 @@
     private ActivityStartInterceptor mInterceptor;
     private ActivityInfo mAInfo = new ActivityInfo();
 
+    private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks =
+            new SparseArray<>();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -157,6 +162,9 @@
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
                 .thenReturn(true);
 
+        // Mock the activity start callbacks
+        when(mService.getActivityInterceptorCallbacks()).thenReturn(mActivityInterceptorCallbacks);
+
         // Initialise activity info
         mAInfo.applicationInfo = new ApplicationInfo();
         mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -285,4 +293,38 @@
         // THEN calling intercept returns false
         assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
     }
+
+    public void addMockInterceptorCallback(@Nullable Intent intent) {
+        int size = mActivityInterceptorCallbacks.size();
+        mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() {
+            @Override
+            public Intent intercept(ActivityInterceptorInfo info) {
+                return intent;
+            }
+        });
+    }
+
+    @Test
+    public void testInterceptionCallback_singleCallback() {
+        addMockInterceptorCallback(new Intent("android.test.foo"));
+
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+        assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
+    }
+
+    @Test
+    public void testInterceptionCallback_singleCallbackReturnsNull() {
+        addMockInterceptorCallback(null);
+
+        assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+    }
+
+    @Test
+    public void testInterceptionCallback_fallbackToSecondCallback() {
+        addMockInterceptorCallback(null);
+        addMockInterceptorCallback(new Intent("android.test.second"));
+
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+        assertEquals("android.test.second", mInterceptor.mIntent.getAction());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 5de4fcb..5d1a068 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -26,6 +26,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -42,12 +44,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.EnterPipRequestedItem;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
@@ -846,7 +850,64 @@
         return wpc;
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterActivityStartInterceptor_IndexTooSmall() {
+        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1,
+                new ActivityInterceptorCallback() {
+                    @Nullable
+                    @Override
+                    public Intent intercept(ActivityInterceptorInfo info) {
+                        return null;
+                    }
+                });
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterActivityStartInterceptor_IndexTooLarge() {
+        mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1,
+                new ActivityInterceptorCallback() {
+                    @Nullable
+                    @Override
+                    public Intent intercept(ActivityInterceptorInfo info) {
+                        return null;
+                    }
+                });
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterActivityStartInterceptor_DuplicateId() {
+        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+                new ActivityInterceptorCallback() {
+                    @Nullable
+                    @Override
+                    public Intent intercept(ActivityInterceptorInfo info) {
+                        return null;
+                    }
+                });
+        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+                new ActivityInterceptorCallback() {
+                    @Nullable
+                    @Override
+                    public Intent intercept(ActivityInterceptorInfo info) {
+                        return null;
+                    }
+                });
+    }
+
+    @Test
+    public void testRegisterActivityStartInterceptor() {
+        assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
+
+        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+                new ActivityInterceptorCallback() {
+                    @Nullable
+                    @Override
+                    public Intent intercept(ActivityInterceptorInfo info) {
+                        return null;
+                    }
+                });
+
+        assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
+        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID));
+    }
 }
-
-
-
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index fd562c3..ebefeaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -75,13 +75,19 @@
 
     @Test
     public void clipAfterAnim_boundsLayerZBoosted() {
+        final Task task = mActivity.getTask();
+        final ActivityRecord topActivity = createActivityRecord(task);
+        task.assignChildLayers(mTransaction);
+
+        assertThat(topActivity.getLastLayer()).isGreaterThan(mActivity.getLastLayer());
+
         mActivity.mNeedsAnimationBoundsLayer = true;
         mActivity.mNeedsZBoost = true;
-
         mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
                 ANIMATION_TYPE_APP_TRANSITION);
+
         verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer),
-                intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE));
+                intThat(layer -> layer > topActivity.getLastLayer()));
     }
 
     @Test
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 24bbf46..79918e2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -73,7 +73,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
@@ -871,18 +870,6 @@
     }
 
     @Test
-    public void testDisableDisplayInfoOverrideFromWindowManager() {
-        final DisplayContent dc = createNewDisplay();
-
-        assertTrue(dc.mShouldOverrideDisplayConfiguration);
-        mWm.dontOverrideDisplayInfo(dc.getDisplayId());
-
-        assertFalse(dc.mShouldOverrideDisplayConfiguration);
-        verify(mWm.mDisplayManagerInternal, times(1))
-                .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
-    }
-
-    @Test
     public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() {
         final DisplayContent portraitDisplay = createNewDisplay();
         portraitDisplay.mInitialDisplayHeight = 2000;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index b5219fd..d09ba29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -91,7 +91,7 @@
     @Before
     public void setUp() {
         final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
-        mTestUserId = um.getUserHandle();
+        mTestUserId = um.getProcessUserId();
 
         final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 4c31ee2..b6701dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.graphics.Point;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -49,10 +48,6 @@
     }
 
     @Override
-    public void locationInParentDisplayChanged(Point offset) throws RemoteException {
-    }
-
-    @Override
     public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 17ae2e8..a482bda 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -80,6 +80,7 @@
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -779,8 +780,7 @@
         @Override
         public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
         @Override
-        public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-                boolean playRevealAnimation) { }
+        public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
         @Override
         public void copySplashScreenView(int taskId) { }
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e6ad68a..a8a9188 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -278,26 +278,6 @@
     }
 
     @Test
-    public void testCanWindowWithEmbeddedDisplayBeImeTarget() {
-        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
-        final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
-
-        imeWindow.setHasSurface(true);
-        appWindow.setHasSurface(true);
-
-        appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
-        assertFalse(appWindow.canBeImeTarget());
-
-        DisplayContent secondDisplay = createNewDisplay();
-        final WindowState embeddedWindow = createWindow(null, TYPE_APPLICATION, secondDisplay,
-                "embeddedWindow");
-        appWindow.addEmbeddedDisplayContent(secondDisplay);
-        embeddedWindow.setHasSurface(true);
-        embeddedWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
-        assertTrue(appWindow.canBeImeTarget());
-    }
-
-    @Test
     public void testGetWindow() {
         final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
         final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 8ec1bd6c..b2d4eea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -77,7 +77,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.os.Bundle;
@@ -101,6 +100,7 @@
 import android.view.WindowManager.DisplayImePolicy;
 import android.window.ITransitionPlayer;
 import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -1457,12 +1457,11 @@
             }
         }
         @Override
-        public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
-                boolean playRevealAnimation) {
+        public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
             synchronized (mWMService.mGlobalLock) {
-                final IBinder appToken = mTaskAppMap.get(taskId);
+                final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
                 if (appToken != null) {
-                    mTaskAppMap.remove(taskId);
+                    mTaskAppMap.remove(removalInfo.taskId);
                     final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
                             appToken);
                     WindowState win = mAppWindowMap.remove(appToken);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index cf4ef58..f0ceff1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -381,8 +381,8 @@
             UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
         }
         synchronized (mLock) {
-            // This should be safe to add this early. Other than reportEventOrAddToQueue, every
-            // other user grabs the lock before accessing
+            // This should be safe to add this early. Other than reportEventOrAddToQueue and
+            // getBackupPayload, every other user grabs the lock before accessing
             // mUserUnlockedStates. reportEventOrAddToQueue does not depend on anything other than
             // mUserUnlockedStates, and the lock will protect the handler.
             mUserUnlockedStates.add(userId);
@@ -1438,8 +1438,8 @@
                     }
                 } else if ("mappings".equals(arg)) {
                     final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-                    final int userId = parseUserIdFromArgs(args, i, ipw);
                     synchronized (mLock) {
+                        final int userId = parseUserIdFromArgs(args, i, ipw);
                         if (userId != UserHandle.USER_NULL) {
                             mUserState.get(userId).dumpMappings(ipw);
                         }
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
old mode 100755
new mode 100644
index f0d43fa..d94fafc
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -564,8 +564,14 @@
          */
         public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x08000000;
 
+        /**
+         * Indicates whether the remote party supports RTT or not to the UI.
+         */
+
+        public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x10000000;
+
         //******************************************************************************************
-        // Next CAPABILITY value: 0x10000000
+        // Next CAPABILITY value: 0x20000000
         //******************************************************************************************
 
         /**
@@ -817,6 +823,9 @@
             if (can(capabilities, CAPABILITY_TRANSFER_CONSULTATIVE)) {
                 builder.append(" CAPABILITY_TRANSFER_CONSULTATIVE");
             }
+            if (can(capabilities, CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT)) {
+                builder.append(" CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT");
+            }
             builder.append("]");
             return builder.toString();
         }
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index bf49f3c..93cd291 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -112,7 +112,7 @@
         if (DBG) Log.d(LOG_TAG, "Trying to get current content resolver...");
 
         final int currentUser = ActivityManager.getCurrentUser();
-        final int myUser = UserManager.get(context).getUserHandle();
+        final int myUser = UserManager.get(context).getProcessUserId();
 
         if (DBG) Log.d(LOG_TAG, "myUser=" + myUser + "currentUser=" + currentUser);
 
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 30fd528..9fec96b 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -422,8 +422,14 @@
      */
     public static final int CAPABILITY_TRANSFER_CONSULTATIVE = 0x10000000;
 
+    /**
+     * Indicates whether the remote party supports RTT or not to the UI.
+     */
+
+    public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x20000000;
+
     //**********************************************************************************************
-    // Next CAPABILITY value: 0x20000000
+    // Next CAPABILITY value: 0x40000000
     //**********************************************************************************************
 
     /**
@@ -1138,6 +1144,10 @@
                 == CAPABILITY_TRANSFER_CONSULTATIVE) {
             builder.append(isLong ? " CAPABILITY_TRANSFER_CONSULTATIVE" : " sup_cTrans");
         }
+        if ((capabilities & CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT)
+                == CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT) {
+            builder.append(isLong ? " CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT" : " sup_rtt");
+        }
         builder.append("]");
         return builder.toString();
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e251322..a4afea1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -315,6 +315,12 @@
      */
     public static final int UNINITIALIZED_CARD_ID = -2;
 
+    /**
+     * Default port index for the UICC Card
+     * @hide
+     */
+    public static final int DEFAULT_PORT_INDEX = 0;
+
     private final Context mContext;
     private final int mSubId;
     @UnsupportedAppUsage
diff --git a/telephony/java/android/telephony/data/EpsQos.java b/telephony/java/android/telephony/data/EpsQos.java
index 22c8b0a..64c956f 100644
--- a/telephony/java/android/telephony/data/EpsQos.java
+++ b/telephony/java/android/telephony/data/EpsQos.java
@@ -32,15 +32,9 @@
 
     int qosClassId;
 
-    public EpsQos() {
-        super(Qos.QOS_TYPE_EPS,
-                new android.hardware.radio.V1_6.QosBandwidth(),
-                new android.hardware.radio.V1_6.QosBandwidth());
-    }
-
-    public EpsQos(@NonNull android.hardware.radio.V1_6.EpsQos qos) {
-        super(Qos.QOS_TYPE_EPS, qos.downlink, qos.uplink);
-        qosClassId = qos.qci;
+    public EpsQos(QosBandwidth downlink, QosBandwidth uplink, int qosClassId) {
+        super(Qos.QOS_TYPE_EPS, downlink, uplink);
+        this.qosClassId = qosClassId;
     }
 
     private EpsQos(Parcel source) {
diff --git a/telephony/java/android/telephony/data/NetworkSlicingConfig.java b/telephony/java/android/telephony/data/NetworkSlicingConfig.java
index dec787f..024a3f5 100644
--- a/telephony/java/android/telephony/data/NetworkSlicingConfig.java
+++ b/telephony/java/android/telephony/data/NetworkSlicingConfig.java
@@ -33,39 +33,15 @@
     private final List<NetworkSliceInfo> mSliceInfo;
 
     public NetworkSlicingConfig() {
-        mUrspRules = new ArrayList<UrspRule>();
-        mSliceInfo = new ArrayList<NetworkSliceInfo>();
+        mUrspRules = new ArrayList<>();
+        mSliceInfo = new ArrayList<>();
     }
 
     /** @hide */
-    public NetworkSlicingConfig(android.hardware.radio.V1_6.SlicingConfig sc) {
-        this(sc.urspRules, sc.sliceInfo);
-    }
-
-    /** @hide */
-    public NetworkSlicingConfig(List<android.hardware.radio.V1_6.UrspRule> urspRules,
-            List<android.hardware.radio.V1_6.SliceInfo> sliceInfo) {
-        mUrspRules = new ArrayList<UrspRule>();
-        for (android.hardware.radio.V1_6.UrspRule ur : urspRules) {
-            mUrspRules.add(new UrspRule(ur.precedence, ur.trafficDescriptors,
-                    ur.routeSelectionDescriptor));
-        }
-        mSliceInfo = new ArrayList<NetworkSliceInfo>();
-        for (android.hardware.radio.V1_6.SliceInfo si : sliceInfo) {
-            mSliceInfo.add(sliceInfoBuilder(si));
-        }
-    }
-
-    private NetworkSliceInfo sliceInfoBuilder(android.hardware.radio.V1_6.SliceInfo si) {
-        NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
-                .setSliceServiceType(si.sst)
-                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
-        if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
-            builder
-                .setSliceDifferentiator(si.sliceDifferentiator)
-                .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
-        }
-        return builder.build();
+    public NetworkSlicingConfig(List<UrspRule> urspRules, List<NetworkSliceInfo> sliceInfo) {
+        this();
+        mUrspRules.addAll(urspRules);
+        mSliceInfo.addAll(sliceInfo);
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
index fe124ac..a24d5fa 100644
--- a/telephony/java/android/telephony/data/NrQos.java
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -32,11 +32,12 @@
     int fiveQi;
     int averagingWindowMs;
 
-    public NrQos(@NonNull android.hardware.radio.V1_6.NrQos qos) {
-        super(Qos.QOS_TYPE_NR, qos.downlink, qos.uplink);
-        fiveQi = qos.fiveQi;
-        qosFlowId = qos.qfi;
-        averagingWindowMs = qos.averagingWindowMs;
+    public NrQos(QosBandwidth downlink, QosBandwidth uplink, int qosFlowId, int fiveQi,
+            int averagingWindowMs) {
+        super(Qos.QOS_TYPE_NR, downlink, uplink);
+        this.qosFlowId = qosFlowId;
+        this.fiveQi = fiveQi;
+        this.averagingWindowMs = averagingWindowMs;
     }
 
     private NrQos(Parcel source) {
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index c286c621..8c437c8 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -48,12 +48,10 @@
     final QosBandwidth downlink;
     final QosBandwidth uplink;
 
-    Qos(int type,
-            @NonNull android.hardware.radio.V1_6.QosBandwidth downlink,
-            @NonNull android.hardware.radio.V1_6.QosBandwidth uplink) {
+    Qos(int type, QosBandwidth downlink, QosBandwidth uplink) {
         this.type = type;
-        this.downlink = new QosBandwidth(downlink.maxBitrateKbps, downlink.guaranteedBitrateKbps);
-        this.uplink = new QosBandwidth(uplink.maxBitrateKbps, uplink.guaranteedBitrateKbps);
+        this.downlink = downlink;
+        this.uplink = uplink;
     }
 
     public QosBandwidth getDownlinkBandwidth() {
@@ -68,10 +66,7 @@
         int maxBitrateKbps;
         int guaranteedBitrateKbps;
 
-        QosBandwidth() {
-        }
-
-        QosBandwidth(int maxBitrateKbps, int guaranteedBitrateKbps) {
+        public QosBandwidth(int maxBitrateKbps, int guaranteedBitrateKbps) {
             this.maxBitrateKbps = maxBitrateKbps;
             this.guaranteedBitrateKbps = guaranteedBitrateKbps;
         }
@@ -158,18 +153,6 @@
     }
 
     /** @hide */
-    public static @NonNull Qos create(@NonNull android.hardware.radio.V1_6.Qos qos) {
-        switch (qos.getDiscriminator()) {
-            case android.hardware.radio.V1_6.Qos.hidl_discriminator.eps:
-                  return new EpsQos(qos.eps());
-            case android.hardware.radio.V1_6.Qos.hidl_discriminator.nr:
-                  return new NrQos(qos.nr());
-            default:
-                  return null;
-        }
-    }
-
-    /** @hide */
     public @QosType int getType() {
         return type;
     }
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 54930d0..5f5762b 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -18,15 +18,12 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.net.InetAddress;
-import java.net.Inet4Address;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -97,20 +94,13 @@
      */
     private int precedence;
 
-    QosBearerFilter() {
-        localAddresses = new ArrayList<>();
-        remoteAddresses = new ArrayList<>();
-        localPort = new PortRange();
-        remotePort = new PortRange();
-        protocol = QOS_PROTOCOL_UNSPECIFIED;
-        filterDirection = QOS_FILTER_DIRECTION_BIDIRECTIONAL;
-    }
-
     public QosBearerFilter(List<LinkAddress> localAddresses, List<LinkAddress> remoteAddresses,
             PortRange localPort, PortRange remotePort, int protocol, int tos,
             long flowLabel, long spi, int direction, int precedence) {
-        this.localAddresses = localAddresses;
-        this.remoteAddresses = remoteAddresses;
+        this.localAddresses = new ArrayList<>();
+        this.localAddresses.addAll(localAddresses);
+        this.remoteAddresses = new ArrayList<>();
+        this.remoteAddresses.addAll(remoteAddresses);
         this.localPort = localPort;
         this.remotePort = remotePort;
         this.protocol = protocol;
@@ -141,82 +131,10 @@
         return precedence;
     }
 
-    /** @hide */
-    public static @NonNull QosBearerFilter create(
-            @NonNull android.hardware.radio.V1_6.QosFilter qosFilter) {
-        QosBearerFilter ret = new QosBearerFilter();
-
-        String[] localAddresses = qosFilter.localAddresses.stream().toArray(String[]::new);
-        if (localAddresses != null) {
-            for (String address : localAddresses) {
-                ret.localAddresses.add(createLinkAddressFromString(address));
-            }
-        }
-
-        String[] remoteAddresses = qosFilter.remoteAddresses.stream().toArray(String[]::new);
-        if (remoteAddresses != null) {
-            for (String address : remoteAddresses) {
-                ret.remoteAddresses.add(createLinkAddressFromString(address));
-            }
-        }
-
-        if (qosFilter.localPort != null) {
-            if (qosFilter.localPort.getDiscriminator()
-                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
-                final android.hardware.radio.V1_6.PortRange portRange = qosFilter.localPort.range();
-                ret.localPort.start = portRange.start;
-                ret.localPort.end = portRange.end;
-            }
-        }
-
-        if (qosFilter.remotePort != null) {
-            if (qosFilter.remotePort.getDiscriminator()
-                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
-                final android.hardware.radio.V1_6.PortRange portRange
-                        = qosFilter.remotePort.range();
-                ret.remotePort.start = portRange.start;
-                ret.remotePort.end = portRange.end;
-            }
-        }
-
-        ret.protocol = qosFilter.protocol;
-
-        if (qosFilter.tos != null) {
-            if (qosFilter.tos.getDiscriminator()
-                == android.hardware.radio.V1_6.QosFilter.TypeOfService.hidl_discriminator.value) {
-                ret.typeOfServiceMask = qosFilter.tos.value();
-            }
-        }
-
-        if (qosFilter.flowLabel != null) {
-            if (qosFilter.flowLabel.getDiscriminator()
-                == android.hardware.radio.V1_6.QosFilter.Ipv6FlowLabel.hidl_discriminator.value) {
-                ret.flowLabel = qosFilter.flowLabel.value();
-            }
-        }
-
-        if (qosFilter.spi != null) {
-            if (qosFilter.spi.getDiscriminator()
-                == android.hardware.radio.V1_6.QosFilter.IpsecSpi.hidl_discriminator.value) {
-                ret.securityParameterIndex = qosFilter.spi.value();
-            }
-        }
-
-        ret.filterDirection = qosFilter.direction;
-        ret.precedence = qosFilter.precedence;
-
-        return ret;
-    }
-
     public static class PortRange implements Parcelable {
         int start;
         int end;
 
-        PortRange() {
-            start = -1;
-            end = -1;
-        }
-
         private PortRange(Parcel source) {
             start = source.readInt();
             end = source.readInt();
@@ -337,32 +255,6 @@
                 && precedence == other.precedence;
     }
 
-    private static LinkAddress createLinkAddressFromString(String addressString) {
-        addressString = addressString.trim();
-        InetAddress address = null;
-        int prefixLength = -1;
-        try {
-            String[] pieces = addressString.split("/", 2);
-            address = InetAddresses.parseNumericAddress(pieces[0]);
-            if (pieces.length == 1) {
-                prefixLength = (address instanceof Inet4Address) ? 32 : 128;
-            } else if (pieces.length == 2) {
-                prefixLength = Integer.parseInt(pieces[1]);
-            }
-        } catch (NullPointerException e) {            // Null string.
-        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
-        } catch (NumberFormatException e) {           // Non-numeric prefix.
-        } catch (IllegalArgumentException e) {        // Invalid IP address.
-        }
-
-        if (address == null || prefixLength == -1) {
-            throw new IllegalArgumentException("Invalid link address " + addressString);
-        }
-
-        return new LinkAddress(address, prefixLength, 0, 0,
-                LinkAddress.LIFETIME_UNKNOWN, LinkAddress.LIFETIME_UNKNOWN);
-    }
-
     private QosBearerFilter(Parcel source) {
         localAddresses = new ArrayList<>();
         source.readList(localAddresses, LinkAddress.class.getClassLoader());
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index 30effc9..ffeb08a 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -36,10 +36,12 @@
     final Qos qos;
     final List<QosBearerFilter> qosBearerFilterList;
 
-    public QosBearerSession(int qosBearerSessionId, @NonNull Qos qos, @NonNull List<QosBearerFilter> qosBearerFilterList) {
+    public QosBearerSession(int qosBearerSessionId, @NonNull Qos qos,
+            @NonNull List<QosBearerFilter> qosBearerFilterList) {
         this.qosBearerSessionId = qosBearerSessionId;
         this.qos = qos;
-        this.qosBearerFilterList = qosBearerFilterList;
+        this.qosBearerFilterList = new ArrayList<>();
+        this.qosBearerFilterList.addAll(qosBearerFilterList);
     }
 
     private QosBearerSession(Parcel source) {
@@ -72,22 +74,6 @@
         dest.writeList(qosBearerFilterList);
     }
 
-    public static @NonNull QosBearerSession create(
-            @NonNull android.hardware.radio.V1_6.QosSession qosSession) {
-        List<QosBearerFilter> qosBearerFilters = new ArrayList<>();
-
-        if (qosSession.qosFilters != null) {
-            for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
-                qosBearerFilters.add(QosBearerFilter.create(filter));
-            }
-        }
-
-        return new QosBearerSession(
-                        qosSession.qosSessionId,
-                        Qos.create(qosSession.qos),
-                        qosBearerFilters);
-    }
-
     @Override
     public int describeContents() {
         return 0;
diff --git a/telephony/java/android/telephony/data/RouteSelectionDescriptor.java b/telephony/java/android/telephony/data/RouteSelectionDescriptor.java
index c2457f2..b3b8464 100644
--- a/telephony/java/android/telephony/data/RouteSelectionDescriptor.java
+++ b/telephony/java/android/telephony/data/RouteSelectionDescriptor.java
@@ -120,43 +120,23 @@
     private final List<String> mDnn;
 
     /** @hide */
-    RouteSelectionDescriptor(android.hardware.radio.V1_6.RouteSelectionDescriptor rsd) {
-        this(rsd.precedence, rsd.sessionType.value(), rsd.sscMode.value(), rsd.sliceInfo,
-                rsd.dnn);
-    }
-
-    /** @hide */
     public RouteSelectionDescriptor(int precedence, int sessionType, int sscMode,
-            List<android.hardware.radio.V1_6.SliceInfo> sliceInfo, List<String> dnn) {
+            List<NetworkSliceInfo> sliceInfo, List<String> dnn) {
         mPrecedence = precedence;
         mSessionType = sessionType;
         mSscMode = sscMode;
-        mSliceInfo = new ArrayList<NetworkSliceInfo>();
-        for (android.hardware.radio.V1_6.SliceInfo si : sliceInfo) {
-            mSliceInfo.add(sliceInfoBuilder(si));
-        }
-        mDnn = new ArrayList<String>();
+        mSliceInfo = new ArrayList<>();
+        mSliceInfo.addAll(sliceInfo);
+        mDnn = new ArrayList<>();
         mDnn.addAll(dnn);
     }
 
-    private NetworkSliceInfo sliceInfoBuilder(android.hardware.radio.V1_6.SliceInfo si) {
-        NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
-                .setSliceServiceType(si.sst)
-                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
-        if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
-            builder
-                .setSliceDifferentiator(si.sliceDifferentiator)
-                .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
-        }
-        return builder.build();
-    }
-
     private RouteSelectionDescriptor(Parcel p) {
         mPrecedence = p.readInt();
         mSessionType = p.readInt();
         mSscMode = p.readInt();
         mSliceInfo = p.createTypedArrayList(NetworkSliceInfo.CREATOR);
-        mDnn = new ArrayList<String>();
+        mDnn = new ArrayList<>();
         p.readStringList(mDnn);
     }
 
diff --git a/telephony/java/android/telephony/data/UrspRule.java b/telephony/java/android/telephony/data/UrspRule.java
index fbe1999..afbd429 100644
--- a/telephony/java/android/telephony/data/UrspRule.java
+++ b/telephony/java/android/telephony/data/UrspRule.java
@@ -19,12 +19,9 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.radio.V1_6.OptionalDnn;
-import android.hardware.radio.V1_6.OptionalOsAppId;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -52,48 +49,14 @@
     private final List<TrafficDescriptor> mTrafficDescriptors;
     private final List<RouteSelectionDescriptor> mRouteSelectionDescriptor;
 
-    UrspRule(android.hardware.radio.V1_6.UrspRule ur) {
-        this(ur.precedence, ur.trafficDescriptors, ur.routeSelectionDescriptor);
-    }
-
     /** @hide */
-    public UrspRule(int precedence,
-            List<android.hardware.radio.V1_6.TrafficDescriptor> trafficDescriptors,
-            List<android.hardware.radio.V1_6.RouteSelectionDescriptor> routeSelectionDescriptor) {
+    public UrspRule(int precedence, List<TrafficDescriptor> trafficDescriptors,
+            List<RouteSelectionDescriptor> routeSelectionDescriptor) {
         mPrecedence = precedence;
-        mTrafficDescriptors = new ArrayList<TrafficDescriptor>();
-        for (android.hardware.radio.V1_6.TrafficDescriptor td : trafficDescriptors) {
-            mTrafficDescriptors.add(convertToTrafficDescriptor(td));
-        }
-        mRouteSelectionDescriptor = new ArrayList<RouteSelectionDescriptor>();
-        for (android.hardware.radio.V1_6.RouteSelectionDescriptor rsd : routeSelectionDescriptor) {
-            mRouteSelectionDescriptor.add(new RouteSelectionDescriptor(rsd));
-        }
-    }
-
-    /** Convert an ArrayList of Bytes to an exactly-sized primitive array */
-    private byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) {
-        byte[] ret = new byte[bytes.size()];
-        for (int i = 0; i < ret.length; i++) {
-            ret[i] = bytes.get(i);
-        }
-        return ret;
-    }
-
-    private TrafficDescriptor convertToTrafficDescriptor(
-            android.hardware.radio.V1_6.TrafficDescriptor td) {
-        String dnn = td.dnn.getDiscriminator() == OptionalDnn.hidl_discriminator.noinit
-                ? null : td.dnn.value();
-        byte[] osAppId = td.osAppId.getDiscriminator() == OptionalOsAppId.hidl_discriminator.noinit
-                ? null : arrayListToPrimitiveArray(td.osAppId.value().osAppId);
-        TrafficDescriptor.Builder builder = new TrafficDescriptor.Builder();
-        if (dnn != null) {
-            builder.setDataNetworkName(dnn);
-        }
-        if (osAppId != null) {
-            builder.setOsAppId(osAppId);
-        }
-        return builder.build();
+        mTrafficDescriptors = new ArrayList<>();
+        mTrafficDescriptors.addAll(trafficDescriptors);
+        mRouteSelectionDescriptor = new ArrayList<>();
+        mRouteSelectionDescriptor.addAll(routeSelectionDescriptor);
     }
 
     private UrspRule(Parcel p) {
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index fadc23b..61c269c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -568,7 +568,7 @@
     int RIL_UNSOL_RINGBACK_TONE = 1029;
     int RIL_UNSOL_RESEND_INCALL_MUTE = 1030;
     int RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 1031;
-    int RIL_UNSOl_CDMA_PRL_CHANGED = 1032;
+    int RIL_UNSOL_CDMA_PRL_CHANGED = 1032;
     int RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE = 1033;
     int RIL_UNSOL_RIL_CONNECTED = 1034;
     int RIL_UNSOL_VOICE_RADIO_TECH_CHANGED = 1035;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 3678f33..663af70 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -27,6 +27,7 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -78,6 +79,11 @@
             }
             transitions {
                 device.reopenAppFromOverview(wmHelper)
+                wmHelper.waitFor(
+                        WindowManagerConditionsFactory.hasLayersAnimating().negate(),
+                        WindowManagerConditionsFactory.isWMStateComplete(),
+                        WindowManagerConditionsFactory.isHomeActivityVisible().negate()
+                )
                 wmHelper.waitForFullScreenApp(testApp.component)
             }
         }
diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
index e4880fd..1341c85 100644
--- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
+++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java
@@ -62,7 +62,7 @@
     public void onEnrollButtonClicked(View v) {
         Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES,
                 Locale.forLanguageTag(BCP47_LOCALE), TEXT,
-                new int[] { UserManager.get(this).getUserHandle() /* current user */});
+                new int[] { UserManager.get(this).getProcessUserId() /* current user */});
         UUID modelUuid = UUID.randomUUID();
         // Generate a fake model to push.
         byte[] data = new byte[1024];