Merge "Always parse sharedUserId and sharedUserLabel" into tm-dev
diff --git a/Android.bp b/Android.bp
index f805947..933d1af 100644
--- a/Android.bp
+++ b/Android.bp
@@ -476,21 +476,28 @@
 }
 
 // TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
-metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
-    "--hide-package com.android.server " +
-    "--hide-package android.audio.policy.configuration.V7_0 " +
-    "--error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide CallbackInterface " +
-    "--hide MissingPermission --hide BroadcastBehavior " +
-    "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
-    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
-    "--error NoSettingsProvider " +
-    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " +
+metalava_framework_docs_args = "" +
     "--api-lint-ignore-prefix android.icu. " +
     "--api-lint-ignore-prefix java. " +
     "--api-lint-ignore-prefix junit. " +
-    "--api-lint-ignore-prefix org. "
+    "--api-lint-ignore-prefix org. " +
+    "--error NoSettingsProvider " +
+    "--error UnhiddenSystemApi " +
+    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " +
+    "--hide BroadcastBehavior " +
+    "--hide CallbackInterface " +
+    "--hide DeprecationMismatch " +
+    "--hide HiddenSuperclass " +
+    "--hide HiddenTypeParameter " +
+    "--hide MissingPermission " +
+    "--hide-package android.audio.policy.configuration.V7_0 " +
+    "--hide-package com.android.server " +
+    "--hide RequiresPermission " +
+    "--hide SdkConstant " +
+    "--hide Todo " +
+    "--hide Typo " +
+    "--hide UnavailableSymbol " +
+    "--manifest $(location core/res/AndroidManifest.xml) "
 
 packages_to_document = [
     "android",
@@ -584,7 +591,7 @@
     libs: [
         "art.module.public.api",
         "sdk_module-lib_current_framework-tethering",
-        "sdk_module-lib_current_framework-connectivity-tiramisu",
+        "sdk_module-lib_current_framework-connectivity-t",
         "sdk_public_current_framework-bluetooth",
         // There are a few classes from modules used by the core that
         // need to be resolved by metalava. We use a prebuilt stub of the
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 726ab2a..94f4374 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -258,7 +258,7 @@
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: [
         "sdk_module-lib_current_framework-tethering",
-        "sdk_module-lib_current_framework-connectivity-tiramisu",
+        "sdk_module-lib_current_framework-connectivity-t",
         "sdk_public_current_framework-bluetooth",
         // NOTE: The below can be removed once the prebuilt stub contains bluetooth.
         "sdk_system_current_android",
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 8c8d2bf..88082f7 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -39,7 +39,9 @@
     /** @hide */
     public static final String KEY_AM_MAX_SATIATED_BALANCE = "am_max_satiated_balance";
     /** @hide */
-    public static final String KEY_AM_MAX_CIRCULATION = "am_max_circulation";
+    public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit";
+    /** @hide */
+    public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit";
     // TODO: Add AlarmManager modifier keys
     /** @hide */
     public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT =
@@ -163,7 +165,9 @@
     public static final String KEY_JS_MAX_SATIATED_BALANCE =
             "js_max_satiated_balance";
     /** @hide */
-    public static final String KEY_JS_MAX_CIRCULATION = "js_max_circulation";
+    public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit";
+    /** @hide */
+    public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit";
     // TODO: Add JobScheduler modifier keys
     /** @hide */
     public static final String KEY_JS_REWARD_TOP_ACTIVITY_INSTANT =
@@ -280,7 +284,9 @@
     /** @hide */
     public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440;
     /** @hide */
-    public static final int DEFAULT_AM_MAX_CIRCULATION = 52000;
+    public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800;
+    /** @hide */
+    public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000;
     // TODO: add AlarmManager modifier default values
     /** @hide */
     public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0;
@@ -359,7 +365,7 @@
     // Default values JobScheduler factors
     // TODO: add time_since_usage variable to min satiated balance factors
     /** @hide */
-    public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 50000;
+    public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000;
     /** @hide */
     public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000;
     /** @hide */
@@ -367,7 +373,9 @@
     /** @hide */
     public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000;
     /** @hide */
-    public static final int DEFAULT_JS_MAX_CIRCULATION = 691200;
+    public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000;
+    /** @hide */
+    public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000;
     // TODO: add JobScheduler modifier default values
     /** @hide */
     public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4e73b02..b936278 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3007,7 +3007,7 @@
                     }
                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                     if (DEBUG) {
-                        Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis());
+                        Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
                     }
                     if (mCharging) {
                         mCharging = false;
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 a6a007f..c0a8148 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -55,9 +55,6 @@
 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.function.Consumer;
@@ -105,54 +102,13 @@
     private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue;
 
     /**
-     * Comparator to use to sort apps before we distribute ARCs so that we try to give the most
-     * important apps ARCs first.
+     * Check the affordability notes of all apps.
      */
-    @VisibleForTesting
-    final Comparator<PackageInfo> mPackageDistributionComparator =
-            new Comparator<PackageInfo>() {
-                @Override
-                public int compare(PackageInfo pi1, PackageInfo pi2) {
-                    final ApplicationInfo appInfo1 = pi1.applicationInfo;
-                    final ApplicationInfo appInfo2 = pi2.applicationInfo;
-                    // Put any packages that don't declare an application at the end. A missing
-                    // <application> tag likely means the app won't be doing any work anyway.
-                    if (appInfo1 == null) {
-                        if (appInfo2 == null) {
-                            return 0;
-                        }
-                        return 1;
-                    } else if (appInfo2 == null) {
-                        return -1;
-                    }
-                    // Privileged apps eat first. They're likely required for the device to
-                    // function properly.
-                    // TODO: include headless system apps
-                    if (appInfo1.isPrivilegedApp()) {
-                        if (!appInfo2.isPrivilegedApp()) {
-                            return -1;
-                        }
-                    } else if (appInfo2.isPrivilegedApp()) {
-                        return 1;
-                    }
-
-                    // Sort by most recently used.
-                    final long timeSinceLastUsedMs1 =
-                            mAppStandbyInternal.getTimeSinceLastUsedByUser(
-                                    pi1.packageName, UserHandle.getUserId(pi1.applicationInfo.uid));
-                    final long timeSinceLastUsedMs2 =
-                            mAppStandbyInternal.getTimeSinceLastUsedByUser(
-                                    pi2.packageName, UserHandle.getUserId(pi2.applicationInfo.uid));
-                    if (timeSinceLastUsedMs1 < timeSinceLastUsedMs2) {
-                        return -1;
-                    } else if (timeSinceLastUsedMs1 > timeSinceLastUsedMs2) {
-                        return 1;
-                    }
-                    return 0;
-                }
-            };
-
-    private static final int MSG_CHECK_BALANCE = 0;
+    private static final int MSG_CHECK_ALL_AFFORDABILITY = 0;
+    /**
+     * Check the affordability notes of a single app.
+     */
+    private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
 
     Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
         mLock = irs.getLock();
@@ -179,7 +135,7 @@
 
         @Override
         public void accept(OngoingEvent ongoingEvent) {
-            mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow);
+            mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow).price;
         }
     }
 
@@ -204,6 +160,11 @@
     }
 
     @GuardedBy("mLock")
+    private boolean isAffordableLocked(long balance, long price, long ctp) {
+        return balance >= price && mScribe.getRemainingConsumableNarcsLocked() >= ctp;
+    }
+
+    @GuardedBy("mLock")
     void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
             final int eventId, @Nullable String tag) {
         if (mIrs.isSystem(userId, pkgName)) {
@@ -218,10 +179,13 @@
         final int eventType = getEventType(eventId);
         switch (eventType) {
             case TYPE_ACTION:
-                final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
+                final EconomicPolicy.Cost actionCost =
+                        economicPolicy.getCostOfAction(eventId, userId, pkgName);
 
                 recordTransactionLocked(userId, pkgName, ledger,
-                        new Ledger.Transaction(now, now, eventId, tag, -actionCost), true);
+                        new Ledger.Transaction(now, now, eventId, tag,
+                                -actionCost.price, actionCost.costToProduce),
+                        true);
                 break;
 
             case TYPE_REWARD:
@@ -231,7 +195,7 @@
                     final long rewardVal = Math.max(0,
                             Math.min(reward.maxDailyReward - rewardSum, reward.instantReward));
                     recordTransactionLocked(userId, pkgName, ledger,
-                            new Ledger.Transaction(now, now, eventId, tag, rewardVal), true);
+                            new Ledger.Transaction(now, now, eventId, tag, rewardVal, 0), true);
                 }
                 break;
 
@@ -268,11 +232,12 @@
         final int eventType = getEventType(eventId);
         switch (eventType) {
             case TYPE_ACTION:
-                final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName);
+                final EconomicPolicy.Cost actionCost =
+                        economicPolicy.getCostOfAction(eventId, userId, pkgName);
 
                 if (ongoingEvent == null) {
                     ongoingEvents.add(eventId, tag,
-                            new OngoingEvent(eventId, tag, null, startElapsed, -actionCost));
+                            new OngoingEvent(eventId, tag, startElapsed, actionCost));
                 } else {
                     ongoingEvent.refCount++;
                 }
@@ -283,7 +248,7 @@
                 if (reward != null) {
                     if (ongoingEvent == null) {
                         ongoingEvents.add(eventId, tag, new OngoingEvent(
-                                eventId, tag, reward, startElapsed, reward.ongoingRewardPerSecond));
+                                eventId, tag, startElapsed, reward));
                     } else {
                         ongoingEvent.refCount++;
                     }
@@ -306,52 +271,7 @@
 
     @GuardedBy("mLock")
     void onPricingChangedLocked() {
-        final long now = getCurrentTimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
-
-        mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
-            final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
-                    mActionAffordabilityNotes.get(userId, pkgName);
-            final boolean[] wasAffordable;
-            if (actionAffordabilityNotes != null) {
-                final int size = actionAffordabilityNotes.size();
-                wasAffordable = new boolean[size];
-                for (int i = 0; i < size; ++i) {
-                    final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
-                    final long originalBalance =
-                            mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
-                    wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice();
-                }
-            } else {
-                wasAffordable = EmptyArray.BOOLEAN;
-            }
-            ongoingEvents.forEach((ongoingEvent) -> {
-                // Disable balance check & affordability notifications here because we're in the
-                // middle of updating ongoing action costs/prices and sending out notifications
-                // or rescheduling the balance check alarm would be a waste since we'll have to
-                // redo them again after all of our internal state is updated.
-                stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
-                        ongoingEvent.tag, nowElapsed, now, false, false);
-                noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
-                        nowElapsed, false);
-            });
-            if (actionAffordabilityNotes != null) {
-                final int size = actionAffordabilityNotes.size();
-                for (int i = 0; i < size; ++i) {
-                    final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
-                    note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
-                    final long newBalance =
-                            mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
-                    final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
-                    if (wasAffordable[i] != isAffordable) {
-                        note.setNewAffordability(isAffordable);
-                        mIrs.postAffordabilityChanged(userId, pkgName, note);
-                    }
-                }
-            }
-            scheduleBalanceCheckLocked(userId, pkgName);
-        });
+        onAnythingChangedLocked(true);
     }
 
     @GuardedBy("mLock")
@@ -365,40 +285,21 @@
             SparseArrayMap<String, OngoingEvent> ongoingEvents =
                     mCurrentOngoingEvents.get(userId, pkgName);
             if (ongoingEvents != null) {
+                mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+                ongoingEvents.forEach(mOngoingEventUpdater);
                 final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
                         mActionAffordabilityNotes.get(userId, pkgName);
-                final boolean[] wasAffordable;
                 if (actionAffordabilityNotes != null) {
                     final int size = actionAffordabilityNotes.size();
-                    wasAffordable = new boolean[size];
+                    final long newBalance =
+                            mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
                     for (int n = 0; n < size; ++n) {
                         final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
-                        final long originalBalance =
-                                mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
-                        wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice();
-                    }
-                } else {
-                    wasAffordable = EmptyArray.BOOLEAN;
-                }
-                ongoingEvents.forEach((ongoingEvent) -> {
-                    // Disable balance check & affordability notifications here because we're in the
-                    // middle of updating ongoing action costs/prices and sending out notifications
-                    // or rescheduling the balance check alarm would be a waste since we'll have to
-                    // redo them again after all of our internal state is updated.
-                    stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId,
-                            ongoingEvent.tag, nowElapsed, now, false, false);
-                    noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag,
-                            nowElapsed, false);
-                });
-                if (actionAffordabilityNotes != null) {
-                    final int size = actionAffordabilityNotes.size();
-                    for (int n = 0; n < size; ++n) {
-                        final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
-                        note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
-                        final long newBalance =
-                                mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
-                        final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
-                        if (wasAffordable[n] != isAffordable) {
+                        note.recalculateCosts(economicPolicy, userId, pkgName);
+                        final boolean isAffordable =
+                                isAffordableLocked(newBalance,
+                                        note.getCachedModifiedPrice(), note.getCtp());
+                        if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
                             mIrs.postAffordabilityChanged(userId, pkgName, note);
                         }
@@ -410,15 +311,68 @@
     }
 
     @GuardedBy("mLock")
+    private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
+        final long now = getCurrentTimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
+
+        for (int uIdx = mCurrentOngoingEvents.numMaps() - 1; uIdx >= 0; --uIdx) {
+            final int userId = mCurrentOngoingEvents.keyAt(uIdx);
+
+            for (int pIdx = mCurrentOngoingEvents.numElementsForKey(userId) - 1; pIdx >= 0;
+                    --pIdx) {
+                final String pkgName = mCurrentOngoingEvents.keyAt(uIdx, pIdx);
+
+                SparseArrayMap<String, OngoingEvent> ongoingEvents =
+                        mCurrentOngoingEvents.valueAt(uIdx, pIdx);
+                if (ongoingEvents != null) {
+                    if (updateOngoingEvents) {
+                        mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+                        ongoingEvents.forEach(mOngoingEventUpdater);
+                    }
+                    scheduleBalanceCheckLocked(userId, pkgName);
+                }
+            }
+        }
+        for (int uIdx = mActionAffordabilityNotes.numMaps() - 1; uIdx >= 0; --uIdx) {
+            final int userId = mActionAffordabilityNotes.keyAt(uIdx);
+
+            for (int pIdx = mActionAffordabilityNotes.numElementsForKey(userId) - 1; pIdx >= 0;
+                    --pIdx) {
+                final String pkgName = mActionAffordabilityNotes.keyAt(uIdx, pIdx);
+
+                final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
+                        mActionAffordabilityNotes.valueAt(uIdx, pIdx);
+
+                if (actionAffordabilityNotes != null) {
+                    final int size = actionAffordabilityNotes.size();
+                    final long newBalance = getBalanceLocked(userId, pkgName);
+                    for (int n = 0; n < size; ++n) {
+                        final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
+                        note.recalculateCosts(economicPolicy, userId, pkgName);
+                        final boolean isAffordable =
+                                isAffordableLocked(newBalance,
+                                        note.getCachedModifiedPrice(), note.getCtp());
+                        if (note.isCurrentlyAffordable() != isAffordable) {
+                            note.setNewAffordability(isAffordable);
+                            mIrs.postAffordabilityChanged(userId, pkgName, note);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
     void stopOngoingActionLocked(final int userId, @NonNull final String pkgName, final int eventId,
             @Nullable String tag, final long nowElapsed, final long now) {
         stopOngoingActionLocked(userId, pkgName, eventId, tag, nowElapsed, now, true, true);
     }
 
     /**
-     * @param updateBalanceCheck          Whether or not to reschedule the affordability/balance
+     * @param updateBalanceCheck          Whether to reschedule the affordability/balance
      *                                    check alarm.
-     * @param notifyOnAffordabilityChange Whether or not to evaluate the app's ability to afford
+     * @param notifyOnAffordabilityChange Whether to evaluate the app's ability to afford
      *                                    registered bills and notify listeners about any changes.
      */
     @GuardedBy("mLock")
@@ -453,9 +407,11 @@
         if (ongoingEvent.refCount <= 0) {
             final long startElapsed = ongoingEvent.startTimeElapsed;
             final long startTime = now - (nowElapsed - startElapsed);
-            final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
+            final EconomicPolicy.Cost actualDelta =
+                    getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now);
             recordTransactionLocked(userId, pkgName, ledger,
-                    new Ledger.Transaction(startTime, now, eventId, tag, actualDelta),
+                    new Ledger.Transaction(startTime, now, eventId, tag, actualDelta.price,
+                            actualDelta.costToProduce),
                     notifyOnAffordabilityChange);
 
             ongoingEvents.delete(eventId, tag);
@@ -466,17 +422,20 @@
     }
 
     @GuardedBy("mLock")
-    private long getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, @NonNull Ledger ledger,
-            long nowElapsed, long now) {
+    @NonNull
+    private EconomicPolicy.Cost getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent,
+            @NonNull Ledger ledger, long nowElapsed, long now) {
         final long startElapsed = ongoingEvent.startTimeElapsed;
         final long durationSecs = (nowElapsed - startElapsed) / 1000;
-        final long computedDelta = durationSecs * ongoingEvent.deltaPerSec;
+        final long computedDelta = durationSecs * ongoingEvent.getDeltaPerSec();
         if (ongoingEvent.reward == null) {
-            return computedDelta;
+            return new EconomicPolicy.Cost(
+                    durationSecs * ongoingEvent.getCtpPerSec(), computedDelta);
         }
         final long rewardSum = ledger.get24HourSum(ongoingEvent.eventId, now);
-        return Math.max(0,
-                Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta));
+        return new EconomicPolicy.Cost(0,
+                Math.max(0,
+                        Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta)));
     }
 
     @VisibleForTesting
@@ -494,22 +453,6 @@
             return;
         }
         final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
-        final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
-        final long curNarcsInCirculation = mScribe.getNarcsInCirculationLocked();
-        final long newArcsInCirculation = curNarcsInCirculation + transaction.delta;
-        if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
-            // Set lower bound at 0 so we don't accidentally take away credits when we were trying
-            // to _give_ the app credits.
-            final long newDelta = Math.max(0, maxCirculationAllowed - curNarcsInCirculation);
-            Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction "
-                    + eventToString(transaction.eventId)
-                    + (transaction.tag == null ? "" : ":" + transaction.tag)
-                    + " for " + appToString(userId, pkgName)
-                    + " by " + narcToString(transaction.delta - newDelta));
-            transaction = new Ledger.Transaction(
-                    transaction.startTimeMs, transaction.endTimeMs,
-                    transaction.eventId, transaction.tag, newDelta);
-        }
         final long originalBalance = ledger.getCurrentBalance();
         if (transaction.delta > 0
                 && originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
@@ -524,10 +467,10 @@
                     + " by " + narcToString(transaction.delta - newDelta));
             transaction = new Ledger.Transaction(
                     transaction.startTimeMs, transaction.endTimeMs,
-                    transaction.eventId, transaction.tag, newDelta);
+                    transaction.eventId, transaction.tag, newDelta, transaction.ctp);
         }
         ledger.recordTransaction(transaction);
-        mScribe.adjustNarcsInCirculationLocked(transaction.delta);
+        mScribe.adjustRemainingConsumableNarcsLocked(-transaction.ctp);
         if (transaction.delta != 0 && notifyOnAffordabilityChange) {
             final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
                     mActionAffordabilityNotes.get(userId, pkgName);
@@ -535,7 +478,9 @@
                 final long newBalance = ledger.getCurrentBalance();
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
-                    final boolean isAffordable = newBalance >= note.getCachedModifiedPrice();
+                    final boolean isAffordable =
+                            isAffordableLocked(newBalance,
+                                    note.getCachedModifiedPrice(), note.getCtp());
                     if (note.isCurrentlyAffordable() != isAffordable) {
                         note.setNewAffordability(isAffordable);
                         mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -543,6 +488,10 @@
                 }
             }
         }
+        if (transaction.ctp != 0) {
+            mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
+            mIrs.maybePerformQuantitativeEasingLocked();
+        }
     }
 
     /**
@@ -599,8 +548,8 @@
                         }
 
                         recordTransactionLocked(userId, pkgName, ledger,
-                                new Ledger.Transaction(
-                                        now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim),
+                                new Ledger.Transaction(now, now, REGULATION_WEALTH_RECLAMATION,
+                                        null, -toReclaim, 0),
                                 true);
                     }
                 }
@@ -648,7 +597,7 @@
             final long now = getCurrentTimeMillis();
             final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
             recordTransactionLocked(userId, pkgName, ledger,
-                    new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim),
+                    new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim, 0),
                     true);
         }
     }
@@ -665,10 +614,13 @@
         return !mIrs.isSystem(userId, packageInfo.packageName);
     }
 
+    void onCreditSupplyChanged() {
+        mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY);
+    }
+
     @GuardedBy("mLock")
     void distributeBasicIncomeLocked(int batteryLevel) {
         List<PackageInfo> pkgs = mIrs.getInstalledPackages();
-        pkgs.sort(mPackageDistributionComparator);
 
         final long now = getCurrentTimeMillis();
         for (int i = 0; i < pkgs.size(); ++i) {
@@ -686,7 +638,7 @@
             if (shortfall > 0) {
                 recordTransactionLocked(userId, pkgName, ledger,
                         new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
-                                null, (long) (perc * shortfall)), true);
+                                null, (long) (perc * shortfall), 0), true);
             }
         }
     }
@@ -705,12 +657,8 @@
     @GuardedBy("mLock")
     void grantBirthrightsLocked(final int userId) {
         final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
-        final long maxBirthright =
-                mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
         final long now = getCurrentTimeMillis();
 
-        pkgs.sort(mPackageDistributionComparator);
-
         for (int i = 0; i < pkgs.size(); ++i) {
             final PackageInfo packageInfo = pkgs.get(i);
             if (!shouldGiveCredits(packageInfo)) {
@@ -726,7 +674,7 @@
 
             recordTransactionLocked(userId, pkgName, ledger,
                     new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
-                            Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))),
+                            mIrs.getMinBalanceLocked(userId, pkgName), 0),
                     true);
         }
     }
@@ -740,14 +688,11 @@
             return;
         }
 
-        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
-        final int numPackages = pkgs.size();
-        final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
         final long now = getCurrentTimeMillis();
 
         recordTransactionLocked(userId, pkgName, ledger,
                 new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,
-                        Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true);
+                        mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
     }
 
     @GuardedBy("mLock")
@@ -762,7 +707,7 @@
         final long now = getCurrentTimeMillis();
 
         recordTransactionLocked(userId, pkgName, ledger,
-                new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true);
+                new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing, 0), true);
     }
 
     @GuardedBy("mLock")
@@ -779,7 +724,7 @@
     private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) {
         final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
         if (ledger.getCurrentBalance() != 0) {
-            mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance());
+            mScribe.adjustRemainingConsumableNarcsLocked(-ledger.getCurrentBalance());
         }
         mScribe.discardLedgerLocked(userId, pkgName);
         mCurrentOngoingEvents.delete(userId, pkgName);
@@ -803,6 +748,7 @@
         static final long WILL_NOT_CROSS_THRESHOLD = -1;
 
         private long mCurBalance;
+        private long mRemainingConsumableCredits;
         /**
          * The maximum change in credits per second towards the upper threshold
          * {@link #mUpperThreshold}. A value of 0 means the current ongoing events will never
@@ -815,15 +761,25 @@
          * result in the app crossing the lower threshold.
          */
         private long mMaxDeltaPerSecToLowerThreshold;
+        /**
+         * The maximum change in credits per second towards the highest CTP threshold below the
+         * remaining consumable credits (cached in {@link #mCtpThreshold}). A value of 0 means
+         * the current ongoing events will never result in the app crossing the lower threshold.
+         */
+        private long mMaxDeltaPerSecToCtpThreshold;
         private long mUpperThreshold;
         private long mLowerThreshold;
+        private long mCtpThreshold;
 
-        void reset(long curBalance,
+        void reset(long curBalance, long remainingConsumableCredits,
                 @Nullable ArraySet<ActionAffordabilityNote> actionAffordabilityNotes) {
             mCurBalance = curBalance;
+            mRemainingConsumableCredits = remainingConsumableCredits;
             mMaxDeltaPerSecToUpperThreshold = mMaxDeltaPerSecToLowerThreshold = 0;
+            mMaxDeltaPerSecToCtpThreshold = 0;
             mUpperThreshold = Long.MIN_VALUE;
             mLowerThreshold = Long.MAX_VALUE;
+            mCtpThreshold = 0;
             if (actionAffordabilityNotes != null) {
                 for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
@@ -835,6 +791,10 @@
                         mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
                                 ? price : Math.min(mUpperThreshold, price);
                     }
+                    final long ctp = note.getCtp();
+                    if (ctp <= mRemainingConsumableCredits) {
+                        mCtpThreshold = Math.max(mCtpThreshold, ctp);
+                    }
                 }
             }
         }
@@ -847,13 +807,23 @@
          * threshold.
          */
         long getTimeToCrossLowerThresholdMs() {
-            if (mMaxDeltaPerSecToLowerThreshold == 0) {
-                // Will never cross upper threshold based on current events.
+            if (mMaxDeltaPerSecToLowerThreshold == 0 && mMaxDeltaPerSecToCtpThreshold == 0) {
+                // Will never cross lower threshold based on current events.
                 return WILL_NOT_CROSS_THRESHOLD;
             }
-            // deltaPerSec is a negative value, so do threshold-balance to cancel out the negative.
-            final long minSeconds =
-                    (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
+            long minSeconds = Long.MAX_VALUE;
+            if (mMaxDeltaPerSecToLowerThreshold != 0) {
+                // deltaPerSec is a negative value, so do threshold-balance to cancel out the
+                // negative.
+                minSeconds = (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold;
+            }
+            if (mMaxDeltaPerSecToCtpThreshold != 0) {
+                minSeconds = Math.min(minSeconds,
+                        // deltaPerSec is a negative value, so do threshold-balance to cancel
+                        // out the negative.
+                        (mCtpThreshold - mRemainingConsumableCredits)
+                                / mMaxDeltaPerSecToCtpThreshold);
+            }
             return minSeconds * 1000;
         }
 
@@ -876,10 +846,15 @@
 
         @Override
         public void accept(OngoingEvent ongoingEvent) {
-            if (mCurBalance >= mLowerThreshold && ongoingEvent.deltaPerSec < 0) {
-                mMaxDeltaPerSecToLowerThreshold += ongoingEvent.deltaPerSec;
-            } else if (mCurBalance < mUpperThreshold && ongoingEvent.deltaPerSec > 0) {
-                mMaxDeltaPerSecToUpperThreshold += ongoingEvent.deltaPerSec;
+            final long deltaPerSec = ongoingEvent.getDeltaPerSec();
+            if (mCurBalance >= mLowerThreshold && deltaPerSec < 0) {
+                mMaxDeltaPerSecToLowerThreshold += deltaPerSec;
+            } else if (mCurBalance < mUpperThreshold && deltaPerSec > 0) {
+                mMaxDeltaPerSecToUpperThreshold += deltaPerSec;
+            }
+            final long ctpPerSec = ongoingEvent.getCtpPerSec();
+            if (mRemainingConsumableCredits >= mCtpThreshold && deltaPerSec < 0) {
+                mMaxDeltaPerSecToCtpThreshold -= ctpPerSec;
             }
         }
     }
@@ -896,8 +871,9 @@
             mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
             return;
         }
-        mTrendCalculator.reset(
-                getBalanceLocked(userId, pkgName), mActionAffordabilityNotes.get(userId, pkgName));
+        mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
+                mScribe.getRemainingConsumableNarcsLocked(),
+                mActionAffordabilityNotes.get(userId, pkgName));
         ongoingEvents.forEach(mTrendCalculator);
         final long lowerTimeMs = mTrendCalculator.getTimeToCrossLowerThresholdMs();
         final long upperTimeMs = mTrendCalculator.getTimeToCrossUpperThresholdMs();
@@ -931,20 +907,79 @@
         public final String tag;
         @Nullable
         public final EconomicPolicy.Reward reward;
-        public final long deltaPerSec;
+        @Nullable
+        public final EconomicPolicy.Cost actionCost;
         public int refCount;
 
-        OngoingEvent(int eventId, @Nullable String tag,
-                @Nullable EconomicPolicy.Reward reward, long startTimeElapsed, long deltaPerSec) {
+        OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
+                @NonNull EconomicPolicy.Reward reward) {
             this.startTimeElapsed = startTimeElapsed;
             this.eventId = eventId;
             this.tag = tag;
             this.reward = reward;
-            this.deltaPerSec = deltaPerSec;
+            this.actionCost = null;
             refCount = 1;
         }
+
+        OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed,
+                @NonNull EconomicPolicy.Cost actionCost) {
+            this.startTimeElapsed = startTimeElapsed;
+            this.eventId = eventId;
+            this.tag = tag;
+            this.reward = null;
+            this.actionCost = actionCost;
+            refCount = 1;
+        }
+
+        long getDeltaPerSec() {
+            if (actionCost != null) {
+                return -actionCost.price;
+            }
+            if (reward != null) {
+                return reward.ongoingRewardPerSecond;
+            }
+            Slog.wtfStack(TAG, "No action or reward in ongoing event?!??!");
+            return 0;
+        }
+
+        long getCtpPerSec() {
+            if (actionCost != null) {
+                return actionCost.costToProduce;
+            }
+            return 0;
+        }
     }
 
+    private class OngoingEventUpdater implements Consumer<OngoingEvent> {
+        private int mUserId;
+        private String mPkgName;
+        private long mNow;
+        private long mNowElapsed;
+
+        private void reset(int userId, String pkgName, long now, long nowElapsed) {
+            mUserId = userId;
+            mPkgName = pkgName;
+            mNow = now;
+            mNowElapsed = nowElapsed;
+        }
+
+        @Override
+        public void accept(OngoingEvent ongoingEvent) {
+            // Disable balance check & affordability notifications here because
+            // we're in the middle of updating ongoing action costs/prices and
+            // sending out notifications or rescheduling the balance check alarm
+            // would be a waste since we'll have to redo them again after all of
+            // our internal state is updated.
+            final boolean updateBalanceCheck = false;
+            stopOngoingActionLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
+                    mNowElapsed, mNow, updateBalanceCheck, /* notifyOnAffordabilityChange */ false);
+            noteOngoingEventLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag,
+                    mNowElapsed, updateBalanceCheck);
+        }
+    }
+
+    private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
+
     private static final class Package {
         public final String packageName;
         public final int userId;
@@ -996,7 +1031,8 @@
         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)
+                mHandler.obtainMessage(
+                        MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
                         .sendToTarget();
             }
         }
@@ -1023,9 +1059,10 @@
                 note.setNewAffordability(true);
                 return;
             }
-            note.recalculateModifiedPrice(economicPolicy, userId, pkgName);
+            note.recalculateCosts(economicPolicy, userId, pkgName);
             note.setNewAffordability(
-                    getBalanceLocked(userId, pkgName) >= note.getCachedModifiedPrice());
+                    isAffordableLocked(getBalanceLocked(userId, pkgName),
+                            note.getCachedModifiedPrice(), note.getCtp()));
             mIrs.postAffordabilityChanged(userId, pkgName, note);
             // Update ongoing alarm
             scheduleBalanceCheckLocked(userId, pkgName);
@@ -1052,6 +1089,7 @@
     static final class ActionAffordabilityNote {
         private final EconomyManagerInternal.ActionBill mActionBill;
         private final EconomyManagerInternal.AffordabilityChangeListener mListener;
+        private long mCtp;
         private long mModifiedPrice;
         private boolean mIsAffordable;
 
@@ -1086,22 +1124,29 @@
             return mModifiedPrice;
         }
 
+        private long getCtp() {
+            return mCtp;
+        }
+
         @VisibleForTesting
-        long recalculateModifiedPrice(@NonNull EconomicPolicy economicPolicy,
+        void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
                 int userId, @NonNull String pkgName) {
             long modifiedPrice = 0;
+            long ctp = 0;
             final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
                     mActionBill.getAnticipatedActions();
             for (int i = 0; i < anticipatedActions.size(); ++i) {
                 final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
 
-                final long actionCost =
+                final EconomicPolicy.Cost actionCost =
                         economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
-                modifiedPrice += actionCost * aa.numInstantaneousCalls
-                        + actionCost * (aa.ongoingDurationMs / 1000);
+                modifiedPrice += actionCost.price * aa.numInstantaneousCalls
+                        + actionCost.price * (aa.ongoingDurationMs / 1000);
+                ctp += actionCost.costToProduce * aa.numInstantaneousCalls
+                        + actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
             }
             mModifiedPrice = modifiedPrice;
-            return modifiedPrice;
+            mCtp = ctp;
         }
 
         boolean isCurrentlyAffordable() {
@@ -1138,7 +1183,15 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_CHECK_BALANCE: {
+                case MSG_CHECK_ALL_AFFORDABILITY: {
+                    synchronized (mLock) {
+                        removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
+                        onAnythingChangedLocked(false);
+                    }
+                }
+                break;
+
+                case MSG_CHECK_INDIVIDUAL_AFFORDABILITY: {
                     final int userId = msg.arg1;
                     final String pkgName = (String) msg.obj;
                     synchronized (mLock) {
@@ -1151,8 +1204,8 @@
                             for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
                                 final ActionAffordabilityNote note =
                                         actionAffordabilityNotes.valueAt(i);
-                                final boolean isAffordable =
-                                        newBalance >= note.getCachedModifiedPrice();
+                                final boolean isAffordable = isAffordableLocked(
+                                        newBalance, note.getCachedModifiedPrice(), note.getCtp());
                                 if (note.isCurrentlyAffordable() != isAffordable) {
                                     note.setNewAffordability(isAffordable);
                                     mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -1207,7 +1260,12 @@
                         pw.print(" runtime=");
                         TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
                         pw.print(" delta/sec=");
-                        pw.print(ongoingEvent.deltaPerSec);
+                        pw.print(narcToString(ongoingEvent.getDeltaPerSec()));
+                        final long ctp = ongoingEvent.getCtpPerSec();
+                        if (ctp != 0) {
+                            pw.print(" ctp/sec=");
+                            pw.print(narcToString(ongoingEvent.getCtpPerSec()));
+                        }
                         pw.print(" refCount=");
                         pw.print(ongoingEvent.refCount);
                         pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index e1e6e47..71e00cf 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -33,7 +33,8 @@
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
-import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -70,7 +71,8 @@
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
-import static android.app.tare.EconomyManager.KEY_AM_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -143,7 +145,8 @@
     private long mMinSatiatedBalanceExempted;
     private long mMinSatiatedBalanceOther;
     private long mMaxSatiatedBalance;
-    private long mMaxSatiatedCirculation;
+    private long mInitialSatiatedConsumptionLimit;
+    private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final InternalResourceService mInternalResourceService;
@@ -179,8 +182,13 @@
     }
 
     @Override
-    long getMaxSatiatedCirculation() {
-        return mMaxSatiatedCirculation;
+    long getInitialSatiatedConsumptionLimit() {
+        return mInitialSatiatedConsumptionLimit;
+    }
+
+    @Override
+    long getHardSatiatedConsumptionLimit() {
+        return mHardSatiatedConsumptionLimit;
     }
 
     @NonNull
@@ -217,8 +225,11 @@
                 DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP));
         mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE,
                 DEFAULT_AM_MAX_SATIATED_BALANCE));
-        mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_AM_MAX_CIRCULATION,
-                DEFAULT_AM_MAX_CIRCULATION));
+        mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
+                KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT));
+        mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
+                arcToNarc(mParser.getInt(
+                        KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT)));
 
         final long exactAllowWhileIdleWakeupBasePrice = arcToNarc(
                 mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
@@ -357,7 +368,11 @@
         pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
-        pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+        pw.print("Consumption limits: [");
+        pw.print(narcToString(mInitialSatiatedConsumptionLimit));
+        pw.print(", ");
+        pw.print(narcToString(mHardSatiatedConsumptionLimit));
+        pw.println("]");
 
         pw.println();
         pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index a4e7b80..2109a85 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -34,7 +34,7 @@
     private final SparseArray<Reward> mRewards = new SparseArray<>();
     private final int[] mCostModifiers;
     private long mMaxSatiatedBalance;
-    private long mMaxSatiatedCirculation;
+    private long mConsumptionLimit;
 
     CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
         super(irs);
@@ -74,9 +74,9 @@
 
         max = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
-            max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedCirculation();
+            max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit();
         }
-        mMaxSatiatedCirculation = max;
+        mConsumptionLimit = max;
     }
 
     @Override
@@ -94,8 +94,13 @@
     }
 
     @Override
-     long getMaxSatiatedCirculation() {
-        return mMaxSatiatedCirculation;
+    long getInitialSatiatedConsumptionLimit() {
+        return mConsumptionLimit;
+    }
+
+    @Override
+    long getHardSatiatedConsumptionLimit() {
+        return mConsumptionLimit;
     }
 
     @NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index c1177b2..1e48015 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -151,6 +151,16 @@
         }
     }
 
+    static class Cost {
+        public final long costToProduce;
+        public final long price;
+
+        Cost(long costToProduce, long price) {
+            this.costToProduce = costToProduce;
+            this.price = price;
+        }
+    }
+
     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
 
     EconomicPolicy(@NonNull InternalResourceService irs) {
@@ -193,10 +203,18 @@
     abstract long getMaxSatiatedBalance();
 
     /**
-     * Returns the maximum number of narcs that should be in circulation at once when the device is
-     * at 100% battery.
+     * Returns the maximum number of narcs that should be consumed during a full 100% discharge
+     * cycle. This is the initial limit. The system may choose to increase the limit over time,
+     * but the increased limit should never exceed the value returned from
+     * {@link #getHardSatiatedConsumptionLimit()}.
      */
-    abstract long getMaxSatiatedCirculation();
+    abstract long getInitialSatiatedConsumptionLimit();
+
+    /**
+     * Returns the maximum number of narcs that should be consumed during a full 100% discharge
+     * cycle. This is the hard limit that should never be exceeded.
+     */
+    abstract long getHardSatiatedConsumptionLimit();
 
     /** Return the set of modifiers that should apply to this policy's costs. */
     @NonNull
@@ -211,10 +229,11 @@
     void dump(IndentingPrintWriter pw) {
     }
 
-    final long getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
+    @NonNull
+    final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
         final Action action = getAction(actionId);
         if (action == null) {
-            return 0;
+            return new Cost(0, 0);
         }
         long ctp = action.costToProduce;
         long price = action.basePrice;
@@ -235,7 +254,7 @@
                     (ProcessStateModifier) getModifier(COST_MODIFIER_PROCESS_STATE);
             price = processStateModifier.getModifiedPrice(userId, pkgName, ctp, price);
         }
-        return price;
+        return new Cost(ctp, price);
     }
 
     private static void initModifier(@Modifier.CostModifier final int modifierId,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 36895a5..c934807 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -69,6 +69,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.tare.EconomicPolicy.Cost;
 import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener;
 
 import java.io.FileDescriptor;
@@ -100,6 +101,11 @@
     private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
     /** The amount of time to delay reclamation by after boot. */
     private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
+    /**
+     * The battery level above which we may consider quantitative easing (increasing the consumption
+     * limit).
+     */
+    private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
     private static final int PACKAGE_QUERY_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_APEX;
@@ -122,44 +128,6 @@
     @GuardedBy("mLock")
     private CompleteEconomicPolicy mCompleteEconomicPolicy;
 
-    private static final class ReclamationConfig {
-        /**
-         * ARC circulation threshold (% circulating vs scaled maximum) above which this config
-         * should come into play.
-         */
-        public final double circulationPercentageThreshold;
-        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
-        public final double reclamationPercentage;
-        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
-        public final long minUsedTimeMs;
-        /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */
-        public final boolean scaleMinBalance;
-
-        ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage,
-                long minUsedTimeMs, boolean scaleMinBalance) {
-            this.circulationPercentageThreshold = circulationPercentageThreshold;
-            this.reclamationPercentage = reclamationPercentage;
-            this.minUsedTimeMs = minUsedTimeMs;
-            this.scaleMinBalance = scaleMinBalance;
-        }
-    }
-
-    /**
-     * Sorted list of reclamation configs used to determine how many credits to force reclaim when
-     * the circulation percentage is too high. The list should *always* be sorted in descending
-     * order of {@link ReclamationConfig#circulationPercentageThreshold}.
-     */
-    @GuardedBy("mLock")
-    private final List<ReclamationConfig> mReclamationConfigs = List.of(
-            new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true),
-            new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true),
-            new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true),
-            new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true),
-            new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false),
-            new ReclamationConfig(
-                    .9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false)
-    );
-
     @NonNull
     @GuardedBy("mLock")
     private final List<PackageInfo> mPkgCache = new ArrayList<>();
@@ -266,8 +234,7 @@
     private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
     private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
     private static final int MSG_PROCESS_USAGE_EVENT = 2;
-    private static final int MSG_MAYBE_FORCE_RECLAIM = 3;
-    private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 4;
+    private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
     private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
 
     /**
@@ -362,8 +329,8 @@
     }
 
     @GuardedBy("mLock")
-    long getMaxCirculationLocked() {
-        return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100;
+    long getConsumptionLimitLocked() {
+        return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100;
     }
 
     @GuardedBy("mLock")
@@ -372,6 +339,11 @@
                 / 100;
     }
 
+    @GuardedBy("mLock")
+    long getInitialSatiatedConsumptionLimitLocked() {
+        return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
+    }
+
     int getUid(final int userId, @NonNull final String pkgName) {
         synchronized (mPackageToUidCache) {
             Integer uid = mPackageToUidCache.get(userId, pkgName);
@@ -403,12 +375,15 @@
     void onBatteryLevelChanged() {
         synchronized (mLock) {
             final int newBatteryLevel = getCurrentBatteryLevel();
-            if (newBatteryLevel > mCurrentBatteryLevel) {
+            final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
+            if (increased) {
                 mAgent.distributeBasicIncomeLocked(newBatteryLevel);
-            } else if (newBatteryLevel < mCurrentBatteryLevel) {
-                mHandler.obtainMessage(MSG_MAYBE_FORCE_RECLAIM).sendToTarget();
+            } else if (newBatteryLevel == mCurrentBatteryLevel) {
+                Slog.wtf(TAG, "Battery level stayed the same");
+                return;
             }
             mCurrentBatteryLevel = newBatteryLevel;
+            adjustCreditSupplyLocked(increased);
         }
     }
 
@@ -546,6 +521,31 @@
         }
     }
 
+    /**
+     * Try to increase the consumption limit if apps are reaching the current limit too quickly.
+     */
+    @GuardedBy("mLock")
+    void maybePerformQuantitativeEasingLocked() {
+        // We don't need to increase the limit if the device runs out of consumable credits
+        // when the battery is low.
+        final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
+        if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD
+                || remainingConsumableNarcs > 0) {
+            return;
+        }
+        final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked();
+        final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
+                * currentConsumptionLimit / 100;
+        final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
+                mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+        if (newConsumptionLimit != currentConsumptionLimit) {
+            Slog.i(TAG, "Increasing consumption limit from " + narcToString(currentConsumptionLimit)
+                    + " to " + narcToString(newConsumptionLimit));
+            mScribe.setConsumptionLimitLocked(newConsumptionLimit);
+            adjustCreditSupplyLocked(/* allowIncrease */ true);
+        }
+    }
+
     void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
             @NonNull Agent.ActionAffordabilityNote affordabilityNote) {
         if (DEBUG) {
@@ -560,6 +560,23 @@
     }
 
     @GuardedBy("mLock")
+    private void adjustCreditSupplyLocked(boolean allowIncrease) {
+        final long newLimit = getConsumptionLimitLocked();
+        final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked();
+        if (remainingConsumableNarcs == newLimit) {
+            return;
+        }
+        if (remainingConsumableNarcs > newLimit) {
+            mScribe.adjustRemainingConsumableNarcsLocked(newLimit - remainingConsumableNarcs);
+        } else if (allowIncrease) {
+            final double perc = mCurrentBatteryLevel / 100d;
+            final long shortfall = newLimit - remainingConsumableNarcs;
+            mScribe.adjustRemainingConsumableNarcsLocked((long) (perc * shortfall));
+        }
+        mAgent.onCreditSupplyChanged();
+    }
+
+    @GuardedBy("mLock")
     private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) {
         if (!mIsEnabled) {
             return;
@@ -650,48 +667,6 @@
         }
     }
 
-    /**
-     * Reclaim unused ARCs above apps' minimum balances if there are too many credits currently
-     * in circulation.
-     */
-    @GuardedBy("mLock")
-    private void maybeForceReclaimLocked() {
-        final long maxCirculation = getMaxCirculationLocked();
-        if (maxCirculation == 0) {
-            Slog.wtf(TAG, "Max scaled circulation is 0...");
-            mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true);
-            mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
-            scheduleUnusedWealthReclamationLocked();
-            return;
-        }
-        final long curCirculation = mScribe.getNarcsInCirculationLocked();
-        final double circulationPerc = 1.0 * curCirculation / maxCirculation;
-        if (DEBUG) {
-            Slog.d(TAG, "Circulation %: " + circulationPerc);
-        }
-        final int numConfigs = mReclamationConfigs.size();
-        if (numConfigs == 0) {
-            return;
-        }
-        // The configs are sorted in descending order of circulationPercentageThreshold, so we can
-        // short-circuit if the current circulation is lower than the lowest threshold.
-        if (circulationPerc
-                < mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) {
-            return;
-        }
-        // TODO: maybe exclude apps we think will be launched in the next few hours
-        for (int i = 0; i < numConfigs; ++i) {
-            final ReclamationConfig config = mReclamationConfigs.get(i);
-            if (circulationPerc >= config.circulationPercentageThreshold) {
-                mAgent.reclaimUnusedAssetsLocked(
-                        config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance);
-                mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
-                scheduleUnusedWealthReclamationLocked();
-                break;
-            }
-        }
-    }
-
     private void registerListeners() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
@@ -731,8 +706,18 @@
             final boolean isFirstSetup = !mScribe.recordExists();
             if (isFirstSetup) {
                 mAgent.grantBirthrightsLocked();
+                mScribe.setConsumptionLimitLocked(
+                        mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
             } else {
                 mScribe.loadFromDiskLocked();
+                if (mScribe.getSatiatedConsumptionLimitLocked()
+                        < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
+                        || mScribe.getSatiatedConsumptionLimitLocked()
+                        > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+                    // Reset the consumption limit since several factors may have changed.
+                    mScribe.setConsumptionLimitLocked(
+                            mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                }
             }
             scheduleUnusedWealthReclamationLocked();
         }
@@ -787,14 +772,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_MAYBE_FORCE_RECLAIM: {
-                    removeMessages(MSG_MAYBE_FORCE_RECLAIM);
-                    synchronized (mLock) {
-                        maybeForceReclaimLocked();
-                    }
-                }
-                break;
-
                 case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
                     final SomeArgs args = (SomeArgs) msg.obj;
                     final int userId = args.argi1;
@@ -936,12 +913,13 @@
             synchronized (mLock) {
                 for (int i = 0; i < projectedActions.size(); ++i) {
                     AnticipatedAction action = projectedActions.get(i);
-                    final long cost = mCompleteEconomicPolicy.getCostOfAction(
+                    final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
                             action.actionId, userId, pkgName);
-                    requiredBalance += cost * action.numInstantaneousCalls
-                            + cost * (action.ongoingDurationMs / 1000);
+                    requiredBalance += cost.price * action.numInstantaneousCalls
+                            + cost.price * (action.ongoingDurationMs / 1000);
                 }
-                return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance;
+                return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance
+                        && mScribe.getRemainingConsumableNarcsLocked() >= requiredBalance;
             }
         }
 
@@ -960,14 +938,17 @@
             synchronized (mLock) {
                 for (int i = 0; i < projectedActions.size(); ++i) {
                     AnticipatedAction action = projectedActions.get(i);
-                    final long cost = mCompleteEconomicPolicy.getCostOfAction(
+                    final Cost cost = mCompleteEconomicPolicy.getCostOfAction(
                             action.actionId, userId, pkgName);
-                    totalCostPerSecond += cost;
+                    totalCostPerSecond += cost.price;
                 }
                 if (totalCostPerSecond == 0) {
                     return FOREVER_MS;
                 }
-                return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond;
+                final long minBalance = Math.min(
+                        mAgent.getBalanceLocked(userId, pkgName),
+                        mScribe.getRemainingConsumableNarcsLocked());
+                return minBalance * 1000 / totalCostPerSecond;
             }
         }
 
@@ -1085,10 +1066,20 @@
 
         private void updateEconomicPolicy() {
             synchronized (mLock) {
+                final long initialLimit =
+                        mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
+                final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
                 mCompleteEconomicPolicy.tearDown();
                 mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
                 if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) {
                     mCompleteEconomicPolicy.setup();
+                    if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
+                            || hardLimit
+                            != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+                        // Reset the consumption limit since several factors may have changed.
+                        mScribe.setConsumptionLimitLocked(
+                                mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                    }
                     mAgent.onPricingChangedLocked();
                 }
             }
@@ -1110,18 +1101,23 @@
             pw.print("Current battery level: ");
             pw.println(mCurrentBatteryLevel);
 
-            final long maxCirculation = getMaxCirculationLocked();
-            pw.print("Max circulation (current/satiated): ");
-            pw.print(narcToString(maxCirculation));
+            final long consumptionLimit = getConsumptionLimitLocked();
+            pw.print("Consumption limit (current/initial-satiated/current-satiated): ");
+            pw.print(narcToString(consumptionLimit));
             pw.print("/");
-            pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation()));
+            pw.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()));
+            pw.print("/");
+            pw.println(narcToString(mScribe.getSatiatedConsumptionLimitLocked()));
 
-            final long currentCirculation = mScribe.getNarcsInCirculationLocked();
-            pw.print("Current GDP: ");
-            pw.print(narcToString(currentCirculation));
+            final long remainingConsumable = mScribe.getRemainingConsumableNarcsLocked();
+            pw.print("Goods remaining: ");
+            pw.print(narcToString(remainingConsumable));
             pw.print(" (");
-            pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation));
-            pw.println("% of current max)");
+            pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit));
+            pw.println("% of current limit)");
+
+            pw.print("Device wealth: ");
+            pw.println(narcToString(mScribe.getNarcsInCirculationForLoggingLocked()));
 
             pw.println();
             pw.print("Exempted apps", mExemptedApps);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 1f8ce26..0eddd22 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -38,7 +38,8 @@
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP;
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
-import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -79,7 +80,8 @@
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
-import static android.app.tare.EconomyManager.KEY_JS_MAX_CIRCULATION;
+import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -145,7 +147,8 @@
     private long mMinSatiatedBalanceExempted;
     private long mMinSatiatedBalanceOther;
     private long mMaxSatiatedBalance;
-    private long mMaxSatiatedCirculation;
+    private long mInitialSatiatedConsumptionLimit;
+    private long mHardSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final InternalResourceService mInternalResourceService;
@@ -181,8 +184,13 @@
     }
 
     @Override
-    long getMaxSatiatedCirculation() {
-        return mMaxSatiatedCirculation;
+    long getInitialSatiatedConsumptionLimit() {
+        return mInitialSatiatedConsumptionLimit;
+    }
+
+    @Override
+    long getHardSatiatedConsumptionLimit() {
+        return mHardSatiatedConsumptionLimit;
     }
 
     @NonNull
@@ -221,8 +229,11 @@
                         DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP));
         mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE,
                 DEFAULT_JS_MAX_SATIATED_BALANCE));
-        mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_JS_MAX_CIRCULATION,
-                DEFAULT_JS_MAX_CIRCULATION));
+        mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt(
+                KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT));
+        mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
+                arcToNarc(mParser.getInt(
+                        KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT)));
 
         mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
                 arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP,
@@ -332,7 +343,11 @@
         pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println();
-        pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println();
+        pw.print("Consumption limits: [");
+        pw.print(narcToString(mInitialSatiatedConsumptionLimit));
+        pw.print(", ");
+        pw.print(narcToString(mHardSatiatedConsumptionLimit));
+        pw.println("]");
 
         pw.println();
         pw.println("Actions:");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index f4917ad..dfdc20a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -41,14 +41,16 @@
         @Nullable
         public final String tag;
         public final long delta;
+        public final long ctp;
 
         Transaction(long startTimeMs, long endTimeMs,
-                int eventId, @Nullable String tag, long delta) {
+                int eventId, @Nullable String tag, long delta, long ctp) {
             this.startTimeMs = startTimeMs;
             this.endTimeMs = endTimeMs;
             this.eventId = eventId;
             this.tag = tag;
             this.delta = delta;
+            this.ctp = ctp;
         }
     }
 
@@ -144,7 +146,10 @@
                 pw.print(")");
             }
             pw.print(" --> ");
-            pw.println(narcToString(transaction.delta));
+            pw.print(narcToString(transaction.delta));
+            pw.print(" (ctp=");
+            pw.print(narcToString(transaction.ctp));
+            pw.println(")");
         }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 86968ef..8662110 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -73,6 +73,7 @@
     private static final String XML_TAG_TRANSACTION = "transaction";
     private static final String XML_TAG_USER = "user";
 
+    private static final String XML_ATTR_CTP = "ctp";
     private static final String XML_ATTR_DELTA = "delta";
     private static final String XML_ATTR_EVENT_ID = "eventId";
     private static final String XML_ATTR_TAG = "tag";
@@ -83,6 +84,8 @@
     private static final String XML_ATTR_USER_ID = "userId";
     private static final String XML_ATTR_VERSION = "version";
     private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
+    private static final String XML_ATTR_REMAINING_CONSUMABLE_NARCS = "remainingConsumableNarcs";
+    private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
 
     /** Version of the file schema. */
     private static final int STATE_FILE_VERSION = 0;
@@ -95,7 +98,9 @@
     @GuardedBy("mIrs.getLock()")
     private long mLastReclamationTime;
     @GuardedBy("mIrs.getLock()")
-    private long mNarcsInCirculation;
+    private long mSatiatedConsumptionLimit;
+    @GuardedBy("mIrs.getLock()")
+    private long mRemainingConsumableNarcs;
     @GuardedBy("mIrs.getLock()")
     private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();
 
@@ -117,10 +122,10 @@
     }
 
     @GuardedBy("mIrs.getLock()")
-    void adjustNarcsInCirculationLocked(long delta) {
+    void adjustRemainingConsumableNarcsLocked(long delta) {
         if (delta != 0) {
             // No point doing any work if the change is 0.
-            mNarcsInCirculation += delta;
+            mRemainingConsumableNarcs += delta;
             postWrite();
         }
     }
@@ -132,6 +137,11 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    long getSatiatedConsumptionLimitLocked() {
+        return mSatiatedConsumptionLimit;
+    }
+
+    @GuardedBy("mIrs.getLock()")
     long getLastReclamationTimeLocked() {
         return mLastReclamationTime;
     }
@@ -153,19 +163,37 @@
         return mLedgers;
     }
 
-    /** Returns the total amount of narcs currently allocated to apps. */
+    /**
+     * Returns the sum of credits granted to all apps on the system. This is expensive so don't
+     * call it for normal operation.
+     */
     @GuardedBy("mIrs.getLock()")
-    long getNarcsInCirculationLocked() {
-        return mNarcsInCirculation;
+    long getNarcsInCirculationForLoggingLocked() {
+        long sum = 0;
+        for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
+            for (int pIdx = mLedgers.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) {
+                sum += mLedgers.valueAt(uIdx, pIdx).getCurrentBalance();
+            }
+        }
+        return sum;
+    }
+
+    /** Returns the total amount of narcs that remain to be consumed. */
+    @GuardedBy("mIrs.getLock()")
+    long getRemainingConsumableNarcsLocked() {
+        return mRemainingConsumableNarcs;
     }
 
     @GuardedBy("mIrs.getLock()")
     void loadFromDiskLocked() {
         mLedgers.clear();
-        mNarcsInCirculation = 0;
         if (!recordExists()) {
+            mSatiatedConsumptionLimit = mIrs.getInitialSatiatedConsumptionLimitLocked();
+            mRemainingConsumableNarcs = mIrs.getConsumptionLimitLocked();
             return;
         }
+        mSatiatedConsumptionLimit = 0;
+        mRemainingConsumableNarcs = 0;
 
         final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
         final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
@@ -222,6 +250,13 @@
                     case XML_TAG_HIGH_LEVEL_STATE:
                         mLastReclamationTime =
                                 parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME);
+                        mSatiatedConsumptionLimit =
+                                parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT,
+                                        mIrs.getInitialSatiatedConsumptionLimitLocked());
+                        final long consumptionLimit = mIrs.getConsumptionLimitLocked();
+                        mRemainingConsumableNarcs = Math.min(consumptionLimit,
+                                parser.getAttributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
+                                        consumptionLimit));
                         break;
                     case XML_TAG_USER:
                         earliestEndTime = Math.min(earliestEndTime,
@@ -249,6 +284,18 @@
     }
 
     @GuardedBy("mIrs.getLock()")
+    void setConsumptionLimitLocked(long limit) {
+        if (mRemainingConsumableNarcs > limit) {
+            mRemainingConsumableNarcs = limit;
+        } else if (limit > mSatiatedConsumptionLimit) {
+            final long diff = mSatiatedConsumptionLimit - mRemainingConsumableNarcs;
+            mRemainingConsumableNarcs = (limit - diff);
+        }
+        mSatiatedConsumptionLimit = limit;
+        postWrite();
+    }
+
+    @GuardedBy("mIrs.getLock()")
     void setLastReclamationTimeLocked(long time) {
         mLastReclamationTime = time;
         postWrite();
@@ -259,7 +306,8 @@
         TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable);
         TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable);
         mLedgers.clear();
-        mNarcsInCirculation = 0;
+        mRemainingConsumableNarcs = 0;
+        mSatiatedConsumptionLimit = 0;
         mLastReclamationTime = 0;
     }
 
@@ -339,13 +387,14 @@
             final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
             final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
             final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+            final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
             if (endTime <= endTimeCutoff) {
                 if (DEBUG) {
                     Slog.d(TAG, "Skipping event because it's too old.");
                 }
                 continue;
             }
-            transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta));
+            transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
         }
 
         if (!isInstalled) {
@@ -395,7 +444,6 @@
                 final Ledger ledger = ledgerData.second;
                 if (ledger != null) {
                     mLedgers.add(curUser, ledgerData.first, ledger);
-                    mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance());
                     final Ledger.Transaction transaction = ledger.getEarliestTransaction();
                     if (transaction != null) {
                         earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
@@ -442,6 +490,9 @@
 
                 out.startTag(null, XML_TAG_HIGH_LEVEL_STATE);
                 out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime);
+                out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit);
+                out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS,
+                        mRemainingConsumableNarcs);
                 out.endTag(null, XML_TAG_HIGH_LEVEL_STATE);
 
                 for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) {
@@ -505,6 +556,7 @@
             out.attribute(null, XML_ATTR_TAG, transaction.tag);
         }
         out.attributeLong(null, XML_ATTR_DELTA, transaction.delta);
+        out.attributeLong(null, XML_ATTR_CTP, transaction.ctp);
         out.endTag(null, XML_TAG_TRANSACTION);
     }
 
diff --git a/api/Android.bp b/api/Android.bp
index 66c7823..bbe26b7 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -113,7 +113,7 @@
         "framework-appsearch",
         "framework-bluetooth",
         "framework-connectivity",
-        "framework-connectivity-tiramisu",
+        "framework-connectivity-t",
         "framework-graphics",
         "framework-media",
         "framework-mediaprovider",
diff --git a/core/api/current.txt b/core/api/current.txt
index 5b1f59a..3a6fc59 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7800,7 +7800,6 @@
   }
 
   public final class DevicePolicyResources {
-    ctor public DevicePolicyResources();
   }
 
   public static final class DevicePolicyResources.Drawables {
@@ -14391,6 +14390,8 @@
     method @NonNull @Size(min=3) public abstract float[] fromXyz(@NonNull @Size(min=3) float[]);
     method @NonNull public static android.graphics.ColorSpace get(@NonNull android.graphics.ColorSpace.Named);
     method @IntRange(from=1, to=4) public int getComponentCount();
+    method public int getDataSpace();
+    method @Nullable public static android.graphics.ColorSpace getFromDataSpace(int);
     method @IntRange(from=android.graphics.ColorSpace.MIN_ID, to=android.graphics.ColorSpace.MAX_ID) public int getId();
     method public abstract float getMaxValue(@IntRange(from=0, to=3) int);
     method public abstract float getMinValue(@IntRange(from=0, to=3) int);
@@ -16870,6 +16871,7 @@
     field public static final long USAGE_SENSOR_DIRECT_DATA = 8388608L; // 0x800000L
     field public static final long USAGE_VIDEO_ENCODE = 65536L; // 0x10000L
     field public static final int YCBCR_420_888 = 35; // 0x23
+    field public static final int YCBCR_P010 = 54; // 0x36
   }
 
   public final class Sensor {
@@ -27445,7 +27447,6 @@
     field public static final String CATEGORY_PAYMENT = "payment";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
-    field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
@@ -30685,7 +30686,7 @@
   public class BaseBundle {
     method public void clear();
     method public boolean containsKey(String);
-    method @Nullable public Object get(String);
+    method @Deprecated @Nullable public Object get(String);
     method public boolean getBoolean(String);
     method public boolean getBoolean(String, boolean);
     method @Nullable public boolean[] getBooleanArray(@Nullable String);
@@ -30927,16 +30928,21 @@
     method public float getFloat(String, float);
     method @Nullable public float[] getFloatArray(@Nullable String);
     method @Nullable public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
-    method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
-    method @Nullable public java.io.Serializable getSerializable(@Nullable String);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String);
+    method @Nullable public <T> T getParcelable(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String);
+    method @Nullable public <T> T[] getParcelableArray(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String);
+    method @Nullable public <T> java.util.ArrayList<T> getParcelableArrayList(@Nullable String, @NonNull Class<T>);
+    method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String);
+    method @Nullable public <T extends java.io.Serializable> T getSerializable(@Nullable String, @NonNull Class<T>);
     method public short getShort(String);
     method public short getShort(String, short);
     method @Nullable public short[] getShortArray(@Nullable String);
     method @Nullable public android.util.Size getSize(@Nullable String);
     method @Nullable public android.util.SizeF getSizeF(@Nullable String);
-    method @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String);
+    method @Nullable public <T> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String, @NonNull Class<T>);
     method @Nullable public java.util.ArrayList<java.lang.String> getStringArrayList(@Nullable String);
     method public boolean hasFileDescriptors();
     method public void putAll(android.os.Bundle);
@@ -49286,7 +49292,7 @@
     method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
-    method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+    method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
     method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 904aa7b..2c0e641 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -205,7 +205,6 @@
     field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
-    field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE";
     field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
     field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -1076,7 +1075,7 @@
   }
 
   public class DevicePolicyManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPreCondition(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
@@ -1129,21 +1128,6 @@
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
-    field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
-    field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
-    field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
-    field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
-    field public static final int CODE_HAS_PAIRED = 8; // 0x8
-    field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
-    field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
-    field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
-    field public static final int CODE_OK = 0; // 0x0
-    field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
-    field public static final int CODE_SYSTEM_USER = 10; // 0xa
-    field public static final int CODE_UNKNOWN_ERROR = -1; // 0xffffffff
-    field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
-    field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
-    field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
     field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1186,6 +1170,21 @@
     field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3
     field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1
     field public static final int STATE_USER_UNMANAGED = 0; // 0x0
+    field public static final int STATUS_ACCOUNTS_NOT_EMPTY = 6; // 0x6
+    field public static final int STATUS_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
+    field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
+    field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
+    field public static final int STATUS_HAS_PAIRED = 8; // 0x8
+    field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
+    field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
+    field public static final int STATUS_NOT_SYSTEM_USER = 7; // 0x7
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
+    field public static final int STATUS_SYSTEM_USER = 10; // 0xa
+    field public static final int STATUS_UNKNOWN_ERROR = -1; // 0xffffffff
+    field public static final int STATUS_USER_HAS_PROFILE_OWNER = 2; // 0x2
+    field public static final int STATUS_USER_NOT_RUNNING = 3; // 0x3
+    field public static final int STATUS_USER_SETUP_COMPLETED = 4; // 0x4
   }
 
   public static final class DevicePolicyResources.Strings {
@@ -6789,7 +6788,8 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
-    method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @Nullable String);
+    method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @NonNull String);
+    method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
@@ -11749,8 +11749,6 @@
 
   public class TraceReportService extends android.app.Service {
     ctor public TraceReportService();
-    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public boolean onMessage(@NonNull android.os.Message);
     method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 29b8248..9757b2f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -525,7 +525,6 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
-    field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
     field public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; // 0x0
     field public static final int DEVICE_OWNER_TYPE_FINANCED = 1; // 0x1
     field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
@@ -569,6 +568,7 @@
     field public static final int OPERATION_SWITCH_USER = 2; // 0x2
     field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28
     field public static final int OPERATION_WIPE_DATA = 8; // 0x8
+    field @Deprecated public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
   }
 
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9a7093e..753df3d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2523,7 +2523,7 @@
     public @interface UserProvisioningState {}
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Unknown error code returned  for {@link #ACTION_PROVISION_MANAGED_DEVICE},
      * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}.
@@ -2531,10 +2531,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_UNKNOWN_ERROR = -1;
+    public static final int STATUS_UNKNOWN_ERROR = -1;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
      * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
@@ -2543,10 +2543,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_OK = 0;
+    public static final int STATUS_OK = 0;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a
      * device owner.
@@ -2554,10 +2554,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_HAS_DEVICE_OWNER = 1;
+    public static final int STATUS_HAS_DEVICE_OWNER = 1;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner
      *  and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
@@ -2565,20 +2565,20 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_USER_HAS_PROFILE_OWNER = 2;
+    public static final int STATUS_USER_HAS_PROFILE_OWNER = 2;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running.
      *
      * @hide
      */
     @SystemApi
-    public static final int CODE_USER_NOT_RUNNING = 3;
+    public static final int STATUS_USER_NOT_RUNNING = 3;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been
      * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
@@ -2586,7 +2586,7 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_USER_SETUP_COMPLETED = 4;
+    public static final int STATUS_USER_SETUP_COMPLETED = 4;
 
     /**
      * Code used to indicate that the device also has a user other than the system user.
@@ -2594,7 +2594,7 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_NONSYSTEM_USER_EXISTS = 5;
+    public static final int STATUS_NONSYSTEM_USER_EXISTS = 5;
 
     /**
      * Code used to indicate that device has an account that prevents provisioning.
@@ -2602,20 +2602,20 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
+    public static final int STATUS_ACCOUNTS_NOT_EMPTY = 6;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user.
      *
      * @hide
      */
     @SystemApi
-    public static final int CODE_NOT_SYSTEM_USER = 7;
+    public static final int STATUS_NOT_SYSTEM_USER = 7;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
      * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired.
@@ -2623,10 +2623,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_HAS_PAIRED = 8;
+    public static final int STATUS_HAS_PAIRED = 8;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} and
      * {@link #ACTION_PROVISION_MANAGED_USER} on devices which do not support managed users.
@@ -2635,10 +2635,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9;
+    public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} if the user is a system user and
      * for {@link #ACTION_PROVISION_MANAGED_DEVICE} on devices running headless system user mode
@@ -2647,10 +2647,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_SYSTEM_USER = 10;
+    public static final int STATUS_SYSTEM_USER = 10;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the user cannot have more
      * managed profiles.
@@ -2658,19 +2658,10 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
+    public static final int STATUS_CANNOT_ADD_MANAGED_PROFILE = 11;
 
     /**
-     * TODO (b/137101239): clean up split system user codes
-     *
-     * @hide
-     * @deprecated not used anymore but can't be removed since it's a @TestApi.
-     **/
-    @Deprecated
-    public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
-
-    /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
      * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device
@@ -2679,21 +2670,21 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
+    public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13;
 
     /**
      * TODO (b/137101239): clean up split system user codes
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * @hide
      * @deprecated not used anymore but can't be removed since it's a @TestApi.
      */
     @Deprecated
     @TestApi
-    public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
+    public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
 
     /**
-     * Result code for {@link #checkProvisioningPreCondition}.
+     * Result code for {@link #checkProvisioningPrecondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
      * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support provisioning.
@@ -2701,24 +2692,24 @@
      * @hide
      */
     @SystemApi
-    public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
+    public static final int STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
 
     /**
-     * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre
+     * Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre
      * conditions.
      *
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "CODE_" }, value = {
-            CODE_UNKNOWN_ERROR, CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER,
-            CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
-            CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
-            CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
-            CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
-            CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN_ERROR, STATUS_OK, STATUS_HAS_DEVICE_OWNER, STATUS_USER_HAS_PROFILE_OWNER,
+            STATUS_USER_NOT_RUNNING, STATUS_USER_SETUP_COMPLETED, STATUS_NOT_SYSTEM_USER,
+            STATUS_HAS_PAIRED, STATUS_MANAGED_USERS_NOT_SUPPORTED, STATUS_SYSTEM_USER,
+            STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED,
+            STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
+            STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS
     })
-    public @interface ProvisioningPreCondition {}
+    public @interface ProvisioningPrecondition {}
 
     /**
      * Disable all configurable SystemUI features during LockTask mode. This includes,
@@ -11996,15 +11987,16 @@
      *        {@link #ACTION_PROVISION_MANAGED_PROFILE}
      * @param packageName The package of the component that would be set as device, user, or profile
      *        owner.
-     * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
+     * @return An int constant value indicating whether provisioning is allowed.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
-    public @ProvisioningPreCondition int checkProvisioningPreCondition(
+    @ProvisioningPrecondition
+    public int checkProvisioningPrecondition(
             @NonNull String action, @NonNull String packageName) {
         try {
-            return mService.checkProvisioningPreCondition(action, packageName);
+            return mService.checkProvisioningPrecondition(action, packageName);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -14689,7 +14681,7 @@
      * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
      * owner.
      *
-     * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK}
+     * <p>The method {@link #checkProvisioningPrecondition} must be returning {@link #STATUS_OK}
      * before calling this method.
      *
      * @param provisioningParams Params required to provision a managed profile,
@@ -14733,7 +14725,7 @@
      * Provisions a managed device and sets the {@code deviceAdminComponentName} as the device
      * owner.
      *
-     * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK}
+     * <p>The method {@link #checkProvisioningPrecondition} must be returning {@link #STATUS_OK}
      * before calling this method.
      *
      * @param provisioningParams Params required to provision a fully managed device,
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 7f2e5fd..042e407 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -317,6 +317,8 @@
  */
 public final class DevicePolicyResources {
 
+    private DevicePolicyResources() {}
+
     /**
      * Resource identifiers used to update device management-related system drawable resources.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0b9d51f..9d28dde 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -373,7 +373,7 @@
             String permission, int grantState, in RemoteCallback resultReceiver);
     int getPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName, String permission);
     boolean isProvisioningAllowed(String action, String packageName);
-    int checkProvisioningPreCondition(String action, String packageName);
+    int checkProvisioningPrecondition(String action, String packageName);
     void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList);
     List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage);
     boolean isManagedProfile(in ComponentName admin);
diff --git a/core/java/android/app/admin/ProvisioningException.java b/core/java/android/app/admin/ProvisioningException.java
index 57a2c50..a457b5f 100644
--- a/core/java/android/app/admin/ProvisioningException.java
+++ b/core/java/android/app/admin/ProvisioningException.java
@@ -46,7 +46,7 @@
     /**
      * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice} and
      * {@link DevicePolicyManager#createAndProvisionManagedProfile}:
-     * Indicates the call to {@link DevicePolicyManager#checkProvisioningPreCondition} returned an
+     * Indicates the call to {@link DevicePolicyManager#checkProvisioningPrecondition} returned an
      * error code.
      */
     public static final int ERROR_PRE_CONDITION_FAILED = 1;
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index ac1bcf3..1e4c9501 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -63,6 +63,7 @@
             D_FP32,
             DS_FP32UI8,
             S_UI8,
+            YCBCR_P010,
     })
     public @interface Format {
     }
@@ -96,6 +97,14 @@
     public static final int DS_FP32UI8   = 0x34;
     /** Format: 8 bits stencil */
     public static final int S_UI8        = 0x35;
+    /**
+     * <p>Android YUV P010 format.</p>
+     *
+     * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
+     * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+     * little-endian value, with the lower 6 bits set to zero.
+     */
+    public static final int YCBCR_P010 = 0x36;
 
     // Note: do not rename, this field is used by native code
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -422,6 +431,7 @@
             case D_FP32:
             case DS_FP32UI8:
             case S_UI8:
+            case YCBCR_P010:
                 return true;
         }
         return false;
diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java
index 99791ce..cd4bf78 100644
--- a/core/java/android/hardware/SyncFence.java
+++ b/core/java/android/hardware/SyncFence.java
@@ -17,6 +17,8 @@
 package android.hardware;
 
 import android.annotation.NonNull;
+import android.media.Image;
+import android.media.ImageWriter;
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSync;
 import android.os.Parcel;
@@ -30,14 +32,29 @@
 import java.time.Duration;
 
 /**
- * A SyncFence represents a synchronization primitive which signals when hardware buffers have
- * completed work on a particular resource.
+ * A SyncFence represents a synchronization primitive which signals when hardware units have
+ * completed work on a particular resource. They initially start in an unsignaled state and make
+ * a one-time transition to either a signaled or error state. SyncFences are created by various
+ * device APIs in response to submitting tasks to the device. They cannot be created nor signaled
+ * by userspace. As a result, this means that a SyncFence will make always make forward progress.
+ *
+ * <p>SyncFence's generally come in one of two varieties. "Presentation fences" refer to
+ *  a SyncFence when the writing to a buffer has finished. "Release fences" then refer
+ *  to when the reading from a buffer has finished.</p>
  *
  * <p>For example, a GPU rendering to a framebuffer may generate a synchronization fence,
- * e.g., a VkFence, which signals when rendering has completed.
+ * e.g., an EGLSync or VkFence, which signals when rendering has completed. Once the fence signals,
+ * then the backing storage for the framebuffer may be safely read from, such as for display or
+ * for media encoding. This would be referred to as a "presentation fence."</p>
  *
- * Once the fence signals, then the backing storage for the framebuffer may be safely read from,
- * such as for display or for media encoding.</p>
+ * <p>Similarly when using an {@link android.media.ImageWriter} it is possible that an
+ * {@link android.media.Image} returned by {@link ImageWriter#dequeueInputImage()} may already
+ * have a {@link Image#getFence() fence} set on it. This would be what is referred to as either
+ * a "release fence" or an "acqurie fence" and indicates the fence that the writer must wait
+ * on before writing to the underlying buffer. In the case of ImageWriter this is done
+ * automatically when eg {@link Image#getPlanes()} is called, however when using
+ * {@link Image#getHardwareBuffer()} it is the caller's responsibility to ensure the
+ * release fence has signaled before writing to the buffer.</p>
  *
  * @see android.opengl.EGLExt#eglDupNativeFenceFDANDROID(EGLDisplay, EGLSync)
  * @see android.media.Image#getFence()
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 2fd79cf..c38a847 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1079,16 +1079,24 @@
     }
 
     /**
-     * Gets the key code produced by the specified location on a US keyboard layout.
-     * Key code as defined in {@link android.view.KeyEvent}.
-     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
-     * which can alter their key mapping using country specific keyboard layouts.
+     * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+     * QWERTY keyboard layout.
+     * <p>
+     * This API is useful for querying the physical location of keys that change the character
+     * produced based on the current locale and keyboard layout.
+     * <p>
+     * @see InputDevice#getKeyCodeForKeyLocation(int) for examples.
      *
-     * @param deviceId The input device id.
-     * @param locationKeyCode The location of a key on a US keyboard layout.
-     * @return The key code produced when pressing the key at the specified location, given the
-     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
-     *         mapping could not be determined, or if an error occurred.
+     * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+     * This provides a consistent way of referring to the physical location of a key independently
+     * of the current keyboard layout. Also see the
+     * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+     * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+     * physical location of a key.
+     * @return The key code produced by the key at the specified location, given the current
+     * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+     * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
+     *
      * @hide
      */
     public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 0a9fe90..9a780c8 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -84,13 +84,6 @@
     public static final String EXTRA_SERVICE_COMPONENT = "component";
 
     /**
-     * The caller userId extra for {@link #ACTION_CHANGE_DEFAULT}.
-     *
-     * @see #ACTION_CHANGE_DEFAULT
-     */
-    public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
-
-    /**
      * Category used for NFC payment services.
      */
     public static final String CATEGORY_PAYMENT = "payment";
diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java
new file mode 100644
index 0000000..2ca3bd2
--- /dev/null
+++ b/core/java/android/os/BadTypeParcelableException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** Used by Parcel to signal that the type on the payload was not expected by the caller. */
+class BadTypeParcelableException extends BadParcelableException {
+    BadTypeParcelableException(String msg) {
+        super(msg);
+    }
+    BadTypeParcelableException(Exception cause) {
+        super(cause);
+    }
+    BadTypeParcelableException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 244335d..45812e5 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -31,7 +33,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 
 /**
  * A mapping from String keys to values of various types. In most cases, you
@@ -254,8 +256,8 @@
         }
         try {
             return getValueAt(0, String.class);
-        } catch (ClassCastException | BadParcelableException e) {
-            typeWarning("getPairValue()", /* value */ null, "String", e);
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning("getPairValue()", "String", e);
             return null;
         }
     }
@@ -320,28 +322,46 @@
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
      *
+     * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in
+     *      other deprecated APIs.
+     *
      * @hide
      */
+    @Deprecated
+    @Nullable
     final Object getValue(String key) {
         return getValue(key, /* clazz */ null);
     }
 
+    /** Same as {@link #getValue(String, Class, Class[])} with no item types. */
+    @Nullable
+    final <T> T getValue(String key, @Nullable Class<T> clazz) {
+        // Avoids allocating Class[0] array
+        return getValue(key, clazz, (Class<?>[]) null);
+    }
+
     /**
-     * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code
+     * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code
      * null} for no type check).
      *
+     * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
+     *
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
      *
      * @hide
      */
-    final <T> T getValue(String key, @Nullable Class<T> clazz) {
+    @Nullable
+    final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
         int i = mMap.indexOfKey(key);
-        return (i >= 0) ? getValueAt(i, clazz) : null;
+        return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null;
     }
 
     /**
-     * Returns the value for a certain position in the array map.
+     * Returns the value for a certain position in the array map for expected return type {@code
+     * clazz} (or pass {@code null} for no type check).
+     *
+     * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}.
      *
      * This call should always be made after {@link #unparcel()} or inside a lock after making sure
      * {@code mMap} is not null.
@@ -349,11 +369,12 @@
      * @hide
      */
     @SuppressWarnings("unchecked")
-    final <T> T getValueAt(int i, @Nullable Class<T> clazz) {
+    @Nullable
+    final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) {
         Object object = mMap.valueAt(i);
-        if (object instanceof Function<?, ?>) {
+        if (object instanceof BiFunction<?, ?, ?>) {
             try {
-                object = ((Function<Class<?>, ?>) object).apply(clazz);
+                object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes);
             } catch (BadParcelableException e) {
                 if (sShouldDefuse) {
                     Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
@@ -615,7 +636,11 @@
      *
      * @param key a String key
      * @return an Object, or null
+     *
+     * @deprecated Use the type-safe specific APIs depending on the type of the item to be
+     *      retrieved, eg. {@link #getString(String)}.
      */
+    @Deprecated
     @Nullable
     public Object get(String key) {
         unparcel();
@@ -623,6 +648,32 @@
     }
 
     /**
+     * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p>Use the more specific APIs where possible, especially in the case of containers such as
+     * lists, since those APIs allow you to specify the type of the items.
+     *
+     * @param key String key
+     * @param clazz The type of the object expected
+     * @return an Object, or null
+     */
+    @Nullable
+    <T> T get(@Nullable String key, @NonNull Class<T> clazz) {
+        unparcel();
+        try {
+            return getValue(key, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, clazz.getCanonicalName(), e);
+            return null;
+        }
+    }
+
+    /**
      * Removes any entry with the given key from the mapping of this Bundle.
      *
      * @param key a String key
@@ -1006,7 +1057,7 @@
             sb.append(" but value was a ");
             sb.append(value.getClass().getName());
         } else {
-            sb.append(" but value was of a different type ");
+            sb.append(" but value was of a different type");
         }
         sb.append(".  The default value ");
         sb.append(defaultValue);
@@ -1019,6 +1070,10 @@
         typeWarning(key, value, className, "<null>", e);
     }
 
+    void typeWarning(String key, String className, RuntimeException e) {
+        typeWarning(key, /* value */ null, className, "<null>", e);
+    }
+
     /**
      * Returns the value associated with the given key, or defaultValue if
      * no mapping of the desired type exists for the given key.
@@ -1358,7 +1413,11 @@
      *
      * @param key a String, or null
      * @return a Serializable value, or null
+     *
+     * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in
+     *      other deprecated APIs.
      */
+    @Deprecated
     @Nullable
     Serializable getSerializable(@Nullable String key) {
         unparcel();
@@ -1375,6 +1434,36 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key a String, or null
+     * @param clazz The expected class of the returned type
+     * @return a Serializable value, or null
+     */
+    @Nullable
+    <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) {
+        return get(key, clazz);
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+        unparcel();
+        try {
+            return getValue(key, ArrayList.class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
@@ -1384,17 +1473,7 @@
      */
     @Nullable
     ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<Integer>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<Integer>", e);
-            return null;
-        }
+        return getArrayList(key, Integer.class);
     }
 
     /**
@@ -1407,17 +1486,7 @@
      */
     @Nullable
     ArrayList<String> getStringArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<String>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<String>", e);
-            return null;
-        }
+        return getArrayList(key, String.class);
     }
 
     /**
@@ -1430,17 +1499,7 @@
      */
     @Nullable
     ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
-        unparcel();
-        Object o = getValue(key);
-        if (o == null) {
-            return null;
-        }
-        try {
-            return (ArrayList<CharSequence>) o;
-        } catch (ClassCastException e) {
-            typeWarning(key, o, "ArrayList<CharSequence>", e);
-            return null;
-        }
+        return getArrayList(key, CharSequence.class);
     }
 
     /**
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 2b13f20..edbbb59 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ArrayMap;
 import android.util.Size;
@@ -876,7 +877,7 @@
     @Nullable
     public Bundle getBundle(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -899,7 +900,11 @@
      *
      * @param key a String, or {@code null}
      * @return a Parcelable value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> T getParcelable(@Nullable String key) {
         unparcel();
@@ -916,30 +921,28 @@
     }
 
     /**
-     * Returns the value associated with the given key, or {@code null} if
-     * no mapping of the desired type exists for the given key or a {@code null}
-     * value is explicitly associated with the key.
+     * Returns the value associated with the given key or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
      *
      * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
      * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
      * Otherwise, this method might throw an exception or return {@code null}.
      *
      * @param key a String, or {@code null}
-     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @param clazz The type of the object expected
      * @return a Parcelable value, or {@code null}
-     *
-     * @hide
      */
     @SuppressWarnings("unchecked")
     @Nullable
     public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
-        unparcel();
-        try {
-            return getValue(key, requireNonNull(clazz));
-        } catch (ClassCastException | BadParcelableException e) {
-            typeWarning(key, /* value */ null, "Parcelable", e);
-            return null;
-        }
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        return get(key, clazz);
     }
 
     /**
@@ -953,7 +956,11 @@
      *
      * @param key a String, or {@code null}
      * @return a Parcelable[] value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from
+     *      Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public Parcelable[] getParcelableArray(@Nullable String key) {
         unparcel();
@@ -970,6 +977,39 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @param clazz The type of the items inside the array
+     * @return a Parcelable[] value, or {@code null}
+     */
+    @SuppressLint({"ArrayReturn", "NullableCollection"})
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        unparcel();
+        try {
+            // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting.
+            return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, clazz.getCanonicalName() + "[]", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or {@code null} if
      * no mapping of the desired type exists for the given key or a {@code null}
      * value is explicitly associated with the key.
@@ -980,7 +1020,11 @@
      *
      * @param key a String, or {@code null}
      * @return an ArrayList<T> value, or {@code null}
+     *
+     * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
         unparcel();
@@ -997,14 +1041,43 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key   a String, or {@code null}
+     * @param clazz The type of the items inside the array list
+     * @return an ArrayList<T> value, or {@code null}
+     */
+    @SuppressLint("NullableCollection")
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> ArrayList<T> getParcelableArrayList(@Nullable String key, @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        return getArrayList(key, clazz);
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
      *
      * @param key a String, or null
-     *
      * @return a SparseArray of T values, or null
+     *
+     * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from
+     *      Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Nullable
     public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
         unparcel();
@@ -1021,13 +1094,44 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key a String, or null
+     * @return a SparseArray of T values, or null
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key,
+            @NonNull Class<T> clazz) {
+        // The reason for not using <T extends Parcelable> is because the caller could provide a
+        // super class to restrict the children that doesn't implement Parcelable itself while the
+        // children do, more details at b/210800751 (same reasoning applies here).
+        unparcel();
+        try {
+            return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz));
+        } catch (ClassCastException | BadTypeParcelableException e) {
+            typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
      *
      * @param key a String, or null
      * @return a Serializable value, or null
+     *
+     * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android
+     *      {@link Build.VERSION_CODES#TIRAMISU}.
      */
+    @Deprecated
     @Override
     @Nullable
     public Serializable getSerializable(@Nullable String key) {
@@ -1035,6 +1139,24 @@
     }
 
     /**
+     * Returns the value associated with the given key, or {@code null} if:
+     * <ul>
+     *     <li>No mapping of the desired type exists for the given key.
+     *     <li>A {@code null} value is explicitly associated with the key.
+     *     <li>The object is not of type {@code clazz}.
+     * </ul>
+     *
+     * @param key   a String, or null
+     * @param clazz The expected class of the returned type
+     * @return a Serializable value, or null
+     */
+    @Nullable
+    public <T extends Serializable> T getSerializable(@Nullable String key,
+            @NonNull Class<T> clazz) {
+        return super.getSerializable(key, requireNonNull(clazz));
+    }
+
+    /**
      * Returns the value associated with the given key, or null if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index ae92353..09cfb6e 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.IntDef;
@@ -65,6 +67,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
@@ -287,26 +290,26 @@
     private static final int VAL_NULL = -1;
     private static final int VAL_STRING = 0;
     private static final int VAL_INTEGER = 1;
-    private static final int VAL_MAP = 2;
+    private static final int VAL_MAP = 2; // length-prefixed
     private static final int VAL_BUNDLE = 3;
-    private static final int VAL_PARCELABLE = 4;
+    private static final int VAL_PARCELABLE = 4; // length-prefixed
     private static final int VAL_SHORT = 5;
     private static final int VAL_LONG = 6;
     private static final int VAL_FLOAT = 7;
     private static final int VAL_DOUBLE = 8;
     private static final int VAL_BOOLEAN = 9;
     private static final int VAL_CHARSEQUENCE = 10;
-    private static final int VAL_LIST  = 11;
-    private static final int VAL_SPARSEARRAY = 12;
+    private static final int VAL_LIST  = 11; // length-prefixed
+    private static final int VAL_SPARSEARRAY = 12; // length-prefixed
     private static final int VAL_BYTEARRAY = 13;
     private static final int VAL_STRINGARRAY = 14;
     private static final int VAL_IBINDER = 15;
-    private static final int VAL_PARCELABLEARRAY = 16;
-    private static final int VAL_OBJECTARRAY = 17;
+    private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed
+    private static final int VAL_OBJECTARRAY = 17; // length-prefixed
     private static final int VAL_INTARRAY = 18;
     private static final int VAL_LONGARRAY = 19;
     private static final int VAL_BYTE = 20;
-    private static final int VAL_SERIALIZABLE = 21;
+    private static final int VAL_SERIALIZABLE = 21; // length-prefixed
     private static final int VAL_SPARSEBOOLEANARRAY = 22;
     private static final int VAL_BOOLEANARRAY = 23;
     private static final int VAL_CHARSEQUENCEARRAY = 24;
@@ -3179,8 +3182,7 @@
      */
     @Deprecated
     public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) {
-        int n = readInt();
-        readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null);
+        readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
     /**
@@ -3195,8 +3197,7 @@
             @NonNull Class<V> clazzValue) {
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
-        int n = readInt();
-        readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+        readMapInternal(outVal, loader, clazzKey, clazzValue);
     }
 
     /**
@@ -3245,13 +3246,7 @@
     @Deprecated
     @Nullable
     public HashMap readHashMap(@Nullable ClassLoader loader) {
-        int n = readInt();
-        if (n < 0) {
-            return null;
-        }
-        HashMap m = new HashMap(n);
-        readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null);
-        return m;
+        return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null);
     }
 
     /**
@@ -3267,13 +3262,7 @@
             @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
         Objects.requireNonNull(clazzKey);
         Objects.requireNonNull(clazzValue);
-        int n = readInt();
-        if (n < 0) {
-            return null;
-        }
-        HashMap<K, V> map = new HashMap<>(n);
-        readMapInternal(map, n, loader, clazzKey, clazzValue);
-        return map;
+        return readHashMapInternal(loader, clazzKey, clazzValue);
     }
 
     /**
@@ -4296,16 +4285,17 @@
 
 
     /**
-     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @see #readValue(int, ClassLoader, Class, Class[])
      */
     @Nullable
-    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
+            @Nullable Class<?>... itemTypes) {
         int type = readInt();
         final T object;
         if (isLengthPrefixed(type)) {
             int length = readInt();
             int start = dataPosition();
-            object = readValue(type, loader, clazz);
+            object = readValue(type, loader, clazz, itemTypes);
             int actual = dataPosition() - start;
             if (actual != length) {
                 Slog.wtfStack(TAG,
@@ -4313,25 +4303,26 @@
                                 + "  consumed " + actual + " bytes, but " + length + " expected.");
             }
         } else {
-            object = readValue(type, loader, clazz);
+            object = readValue(type, loader, clazz, itemTypes);
         }
         return object;
     }
 
     /**
-     * This will return a {@link Function} for length-prefixed types that deserializes the object
-     * when {@link Function#apply} is called with the expected class of the return object (or {@code
-     * null} for no type check), for other types it will return the object itself.
+     * This will return a {@link BiFunction} for length-prefixed types that deserializes the object
+     * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link
+     * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it
+     * will return the object itself.
      *
-     * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that
-     * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No
+     * <p>After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you
+     * shouldn't recycle the parcel, not at least until all objects have been retrieved. No
      * synchronization attempts are made.
      *
      * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
      * function objects are equal if either of the following is true:
      * <ul>
-     *   <li>{@link Function#apply} has been called on both and both objects returned are equal.
-     *   <li>{@link Function#apply} hasn't been called on either one and everything below is true:
+     *   <li>{@link BiFunction#apply} has been called on both and both objects returned are equal.
+     *   <li>{@link BiFunction#apply} hasn't been called on either one and everything below is true:
      *   <ul>
      *       <li>The {@code loader} parameters used to retrieve each are equal.
      *       <li>They both have the same type.
@@ -4358,7 +4349,7 @@
     }
 
 
-    private static final class LazyValue implements Function<Class<?>, Object> {
+    private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> {
         /**
          *                      |   4B   |   4B   |
          * mSource = Parcel{... |  type  | length | object | ...}
@@ -4390,7 +4381,7 @@
         }
 
         @Override
-        public Object apply(@Nullable Class<?> clazz) {
+        public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
             Parcel source = mSource;
             if (source != null) {
                 synchronized (source) {
@@ -4399,7 +4390,7 @@
                         int restore = source.dataPosition();
                         try {
                             source.setDataPosition(mPosition);
-                            mObject = source.readValue(mLoader, clazz);
+                            mObject = source.readValue(mLoader, clazz, itemTypes);
                         } finally {
                             source.setDataPosition(restore);
                         }
@@ -4479,14 +4470,25 @@
         }
     }
 
+    /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        // Avoids allocating Class[0] array
+        return readValue(type, loader, clazz, (Class<?>[]) null);
+    }
+
     /**
      * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
      * type first.
+     *
      * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @param itemTypes If the value is a container, these represent the item types (eg. for a list
+     *                  it's the item type, for a map, it's the key type, followed by the value
+     *                  type).
      */
     @SuppressWarnings("unchecked")
     @Nullable
-    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz,
+            @Nullable Class<?>... itemTypes) {
         final Object object;
         switch (type) {
             case VAL_NULL:
@@ -4502,7 +4504,11 @@
                 break;
 
             case VAL_MAP:
-                object = readHashMap(loader);
+                checkTypeToUnparcel(clazz, HashMap.class);
+                Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0);
+                Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1);
+                checkArgument((keyType == null) == (valueType == null));
+                object = readHashMapInternal(loader, keyType, valueType);
                 break;
 
             case VAL_PARCELABLE:
@@ -4533,10 +4539,12 @@
                 object = readCharSequence();
                 break;
 
-            case VAL_LIST:
-                object = readArrayList(loader);
+            case VAL_LIST: {
+                checkTypeToUnparcel(clazz, ArrayList.class);
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                object = readArrayListInternal(loader, itemType);
                 break;
-
+            }
             case VAL_BOOLEANARRAY:
                 object = createBooleanArray();
                 break;
@@ -4557,10 +4565,12 @@
                 object = readStrongBinder();
                 break;
 
-            case VAL_OBJECTARRAY:
-                object = readArray(loader);
+            case VAL_OBJECTARRAY: {
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class);
+                object = readArrayInternal(loader, itemType);
                 break;
-
+            }
             case VAL_INTARRAY:
                 object = createIntArray();
                 break;
@@ -4577,14 +4587,18 @@
                 object = readSerializableInternal(loader, clazz);
                 break;
 
-            case VAL_PARCELABLEARRAY:
-                object = readParcelableArray(loader);
+            case VAL_PARCELABLEARRAY: {
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class);
+                object = readParcelableArrayInternal(loader, itemType);
                 break;
-
-            case VAL_SPARSEARRAY:
-                object = readSparseArray(loader);
+            }
+            case VAL_SPARSEARRAY: {
+                checkTypeToUnparcel(clazz, SparseArray.class);
+                Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0);
+                object = readSparseArrayInternal(loader, itemType);
                 break;
-
+            }
             case VAL_SPARSEBOOLEANARRAY:
                 object = readSparseBooleanArray();
                 break;
@@ -4632,7 +4646,7 @@
                             + " at offset " + off);
         }
         if (object != null && clazz != null && !clazz.isInstance(object)) {
-            throw new BadParcelableException("Unparcelled object " + object
+            throw new BadTypeParcelableException("Unparcelled object " + object
                     + " is not an instance of required class " + clazz.getName()
                     + " provided in the parameter");
         }
@@ -4659,6 +4673,38 @@
     }
 
     /**
+     * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of
+     * {@code requiredArrayType}.
+     */
+    private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType,
+            Class<?> componentTypeToUnparcel) {
+        if (requiredArrayType != null) {
+            // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check
+            Class<?> requiredComponentType = requiredArrayType.getComponentType();
+            if (requiredComponentType == null) {
+                throw new BadTypeParcelableException(
+                        "About to unparcel an array but type "
+                                + requiredArrayType.getCanonicalName()
+                                + " required by caller is not an array.");
+            }
+            checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel);
+        }
+    }
+
+    /**
+     * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code
+     * requiredType} is not {@code null}.
+     */
+    private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) {
+        if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) {
+            throw new BadTypeParcelableException(
+                    "About to unparcel a " + typeToUnparcel.getCanonicalName()
+                            + ", which is not a subtype of type " + requiredType.getCanonicalName()
+                            + " required by caller.");
+        }
+    }
+
+    /**
      * Read and return a new Parcelable from the parcel.  The given class loader
      * will be used to load any enclosed Parcelables.  If it is null, the default
      * class loader will be used.
@@ -4788,7 +4834,7 @@
             if (clazz != null) {
                 Class<?> parcelableClass = creator.getClass().getEnclosingClass();
                 if (!clazz.isAssignableFrom(parcelableClass)) {
-                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                    throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
                             + "a subclass of required class " + clazz.getName()
                             + " provided in the parameter");
                 }
@@ -4811,7 +4857,7 @@
             }
             if (clazz != null) {
                 if (!clazz.isAssignableFrom(parcelableClass)) {
-                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                    throw new BadTypeParcelableException("Parcelable creator " + name + " is not "
                             + "a subclass of required class " + clazz.getName()
                             + " provided in the parameter");
                 }
@@ -4872,15 +4918,7 @@
     @Deprecated
     @Nullable
     public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N < 0) {
-            return null;
-        }
-        Parcelable[] p = new Parcelable[N];
-        for (int i = 0; i < N; i++) {
-            p[i] = readParcelable(loader);
-        }
-        return p;
+        return readParcelableArrayInternal(loader, /* clazz */ null);
     }
 
     /**
@@ -4892,14 +4930,20 @@
      * trying to instantiate an element.
      */
     @SuppressLint({"ArrayReturn", "NullableCollection"})
-    @SuppressWarnings("unchecked")
     @Nullable
     public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        return readParcelableArrayInternal(loader, requireNonNull(clazz));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader,
+            @Nullable Class<T> clazz) {
         int n = readInt();
         if (n < 0) {
             return null;
         }
-        T[] p = (T[]) Array.newInstance(clazz, n);
+        T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n));
         for (int i = 0; i < n; i++) {
             p[i] = readParcelableInternal(loader, clazz);
         }
@@ -4962,7 +5006,7 @@
                 // the class the same way as ObjectInputStream, using the provided classloader.
                 Class<?> cl = Class.forName(name, false, loader);
                 if (!clazz.isAssignableFrom(cl)) {
-                    throw new BadParcelableException("Serializable object "
+                    throw new BadTypeParcelableException("Serializable object "
                             + cl.getName() + " is not a subclass of required class "
                             + clazz.getName() + " provided in the parameter");
                 }
@@ -4987,7 +5031,7 @@
                 // the deserialized object, as we cannot resolve the class the same way as
                 // ObjectInputStream.
                 if (!clazz.isAssignableFrom(object.getClass())) {
-                    throw new BadParcelableException("Serializable object "
+                    throw new BadTypeParcelableException("Serializable object "
                             + object.getClass().getName() + " is not a subclass of required class "
                             + clazz.getName() + " provided in the parameter");
                 }
@@ -5097,7 +5141,26 @@
         readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null);
     }
 
-    /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
+    @Nullable
+    private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader,
+            @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
+        int n = readInt();
+        if (n < 0) {
+            return null;
+        }
+        HashMap<K, V> map = new HashMap<>(n);
+        readMapInternal(map, n, loader, clazzKey, clazzValue);
+        return map;
+    }
+
+    private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal,
+            @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
+            @Nullable Class<V> clazzValue) {
+        int n = readInt();
+        readMapInternal(outVal, n, loader, clazzKey, clazzValue);
+    }
+
+    private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n,
             @Nullable ClassLoader loader, @Nullable Class<K> clazzKey,
             @Nullable Class<V> clazzValue) {
         while (n > 0) {
@@ -5108,7 +5171,7 @@
         }
     }
 
-    /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
+    private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
             int size, @Nullable ClassLoader loader) {
         readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
     }
diff --git a/core/java/android/service/tracing/TraceReportService.java b/core/java/android/service/tracing/TraceReportService.java
index 3d16a3d..6fdc8e8 100644
--- a/core/java/android/service/tracing/TraceReportService.java
+++ b/core/java/android/service/tracing/TraceReportService.java
@@ -112,7 +112,6 @@
         }
     }
 
-    // Methods to override.
     /**
      * Called when a trace is reported and sent to this class.
      *
@@ -123,15 +122,10 @@
     public void onReportTrace(@NonNull TraceParams args) {
     }
 
-    // Optional methods to override.
-    // Realistically, these methods are internal implementation details but since this class is
-    // a SystemApi, it's better to err on the side of flexibility just in-case we need to override
-    // these methods down the line.
-
     /**
      * Handles binder calls from system_server.
      */
-    public boolean onMessage(@NonNull Message msg) {
+    private boolean onMessage(@NonNull Message msg) {
         if (msg.what == MSG_REPORT_TRACE) {
             if (!(msg.obj instanceof TraceReportParams)) {
                 Log.e(TAG, "Received invalid type for report trace message.");
@@ -153,10 +147,12 @@
 
     /**
      * Returns an IBinder for handling binder calls from system_server.
+     *
+     * @hide
      */
     @Nullable
     @Override
-    public IBinder onBind(@NonNull Intent intent) {
+    public final IBinder onBind(@NonNull Intent intent) {
         if (mMessenger == null) {
             mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
         }
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 3fc9f6b..b48b525 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -54,13 +54,11 @@
 
     @NonNull
     private InputMethodManagerDelegate getImmDelegate() {
-        InputMethodManagerDelegate delegate = mDelegate;
-        if (delegate != null) {
-            return delegate;
+        if (mDelegate == null) {
+            mDelegate = mViewRootImpl.mContext.getSystemService(
+                    InputMethodManager.class).getDelegate();
         }
-        delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
-        mDelegate = delegate;
-        return delegate;
+        return mDelegate;
     }
 
     /** Called when the view root is moved to a different display. */
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 188d745..7d56039 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -737,15 +737,47 @@
     }
 
     /**
-     * Gets the key code produced by the specified location on a US keyboard layout.
-     * Key code as defined in {@link android.view.KeyEvent}.
-     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
-     * which can alter their key mapping using country specific keyboard layouts.
+     * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference
+     * QWERTY keyboard layout.
+     * <p>
+     * This API is useful for querying the physical location of keys that change the character
+     * produced based on the current locale and keyboard layout.
+     * <p>
+     * The following table provides a non-exhaustive list of examples:
+     * <table border="2" width="85%" align="center" cellpadding="5">
+     *     <thead>
+     *         <tr><th>Active Keyboard Layout</th> <th>Input Parameter</th>
+     *         <th>Return Value</th></tr>
+     *     </thead>
      *
-     * @param locationKeyCode The location of a key on a US keyboard layout.
-     * @return The key code produced when pressing the key at the specified location, given the
-     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
-     *         mapping could not be determined, or if an error occurred.
+     *     <tbody>
+     *     <tr>
+     *         <td>French AZERTY</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Q}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_A}</code></td>
+     *     </tr>
+     *     <tr>
+     *         <td>German QWERTZ</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Y}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_Z}</code></td>
+     *     </tr>
+     *     <tr>
+     *         <td>US QWERTY</td>
+     *         <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+     *         <td><code>{@link KeyEvent#KEYCODE_B}</code></td>
+     *     </tr>
+     *     </tbody>
+     * </table>
+     *
+     * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout.
+     * This provides a consistent way of referring to the physical location of a key independently
+     * of the current keyboard layout. Also see the
+     * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system">
+     * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the
+     * physical location of a key.
+     * @return The key code produced by the key at the specified location, given the current
+     * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify
+     * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined.
      */
     public int getKeyCodeForKeyLocation(int locationKeyCode) {
         return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 98cef958..632af23 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3094,6 +3094,10 @@
          * @param destFrame The destination rectangle in parent space. Or null for the source frame.
          * @param orientation The buffer rotation
          * @return This transaction object.
+         * @deprecated Use {@link #setCrop(SurfaceControl, Rect)},
+         * {@link #setBufferTransform(SurfaceControl, int)},
+         * {@link #setPosition(SurfaceControl, float, float)} and
+         * {@link #setScale(SurfaceControl, float, float)} instead.
          */
         @NonNull
         public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4dc1fca..34a1386 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14173,7 +14173,7 @@
                 && isAccessibilityPane()) {
             // If the pane isn't visible, content changed events are sufficient unless we're
             // reporting that the view just disappeared
-            if ((getVisibility() == VISIBLE)
+            if ((isAggregatedVisible())
                     || (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) {
                 final AccessibilityEvent event = AccessibilityEvent.obtain();
                 onInitializeAccessibilityEvent(event);
@@ -21142,6 +21142,10 @@
         }
 
         notifyEnterOrExitForAutoFillIfNeeded(false);
+
+        if (info != null && !collectPreferKeepClearRects().isEmpty()) {
+            info.mViewRootImpl.updateKeepClearRectsForView(this);
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1c27046..8ee3e43 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3222,7 +3222,8 @@
 
         /**
          * The window is allowed to extend into the {@link DisplayCutout} area, only if the
-         * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is
+         * {@link DisplayCutout} is fully contained within a system bar or the {@link DisplayCutout}
+         * is not deeper than 16 dp, but this depends on the OEM choice. Otherwise, the window is
          * laid out such that it does not overlap with the {@link DisplayCutout} area.
          *
          * <p>
@@ -3237,6 +3238,13 @@
          * The usual precautions for not overlapping with the status and navigation bar are
          * sufficient for ensuring that no important content overlaps with the DisplayCutout.
          *
+         * <p>
+         * Note: OEMs can have an option to allow the window to always extend into the
+         * {@link DisplayCutout} area, no matter the cutout flag set, when the {@link DisplayCutout}
+         * is on the different side from system bars, only if the {@link DisplayCutout} overlaps at
+         * most 16dp with the windows.
+         * In such case, OEMs must provide an opt-in/out affordance for users.
+         *
          * @see DisplayCutout
          * @see WindowInsets
          * @see #layoutInDisplayCutoutMode
@@ -3249,8 +3257,16 @@
          * The window is always allowed to extend into the {@link DisplayCutout} areas on the short
          * edges of the screen.
          *
+         * <p>
          * The window will never extend into a {@link DisplayCutout} area on the long edges of the
-         * screen.
+         * screen, unless the {@link DisplayCutout} is not deeper than 16 dp, but this depends on
+         * the OEM choice.
+         *
+         * <p>
+         * Note: OEMs can have an option to allow the window to extend into the
+         * {@link DisplayCutout} area on the long edge side, only if the cutout overlaps at most
+         * 16dp with the windows. In such case, OEMs must provide an opt-in/out affordance for
+         * users.
          *
          * <p>
          * The window must make sure that no important content overlaps with the
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2359d8d..2717463 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2031,14 +2031,20 @@
      * @param inputConnection the connection to be invalidated.
      * @param textSnapshot {@link TextSnapshot} to be used to update {@link EditorInfo}.
      * @param sessionId the session ID to be sent.
+     * @return {@code true} if the operation is done. {@code false} if the caller needs to fall back
+     *         to {@link InputMethodManager#restartInput(View)}.
      * @hide
      */
-    public void doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection,
+    public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection,
             @NonNull TextSnapshot textSnapshot, int sessionId) {
         synchronized (mH) {
             if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) {
                 // OK to ignore because the calling InputConnection is already abandoned.
-                return;
+                return true;
+            }
+            if (mCurrentInputMethodSession == null) {
+                // IME is not yet bound to the client.  Need to fall back to the restartInput().
+                return false;
             }
             final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal();
             editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart();
@@ -2051,6 +2057,7 @@
                     sessionId);
             forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo,
                     mServedInputConnection, sessionId));
+            return true;
         }
     }
 
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 5bcfa8b..1da2af4 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.S;
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU;
 
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index cf6807e..0ef37d1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -51,7 +51,7 @@
     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
     /** @hide */
     private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForS";
+            "com.android.webview.chromium.WebViewChromiumFactoryProviderForT";
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3dfb4a5..c207af5 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12774,7 +12774,9 @@
     /**
      * Called when a context menu option for the text view is selected.  Currently
      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
-     * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
+     * {@link android.R.id#copy}, {@link android.R.id#paste},
+     * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or
+     * {@link android.R.id#shareText}.
      *
      * @return true if the context menu item action was performed.
      */
@@ -12965,6 +12967,7 @@
      * method. The default actions can also be removed from the menu using
      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
+     * {@link android.R.id#pasteAsPlainText} (starting at API level 23),
      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
      *
      * <p>Returning false from
@@ -13003,7 +13006,8 @@
      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
      * android.view.Menu)} method. The default actions can also be removed from the menu using
      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
-     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
+     * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API
+     * level 23) or {@link android.R.id#replaceText} ids as parameters.</p>
      *
      * <p>Returning false from
      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
index 7108d14..23d966fd 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -16,25 +16,26 @@
 
 package com.android.internal.app;
 
-import com.android.internal.R;
-
-import android.app.Dialog;
+import android.app.AlertDialog;
 import android.content.Context;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.Window;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
+import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 
+import com.android.internal.R;
+
 import java.util.Comparator;
 
 /**
@@ -48,9 +49,10 @@
  *
  * TODO: Move this back into the API, as in the support library media router.
  */
-public class MediaRouteChooserDialog extends Dialog {
+public class MediaRouteChooserDialog extends AlertDialog {
     private final MediaRouter mRouter;
     private final MediaRouterCallback mCallback;
+    private final boolean mShowProgressBarWhenEmpty;
 
     private int mRouteTypes;
     private View.OnClickListener mExtendedSettingsClickListener;
@@ -60,10 +62,15 @@
     private boolean mAttachedToWindow;
 
     public MediaRouteChooserDialog(Context context, int theme) {
+        this(context, theme, true);
+    }
+
+    public MediaRouteChooserDialog(Context context, int theme, boolean showProgressBarWhenEmpty) {
         super(context, theme);
 
         mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
         mCallback = new MediaRouterCallback();
+        mShowProgressBarWhenEmpty = showProgressBarWhenEmpty;
     }
 
     /**
@@ -120,28 +127,38 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+        // Note: setView must be called before super.onCreate().
+        setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog,
+                null));
 
-        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
-
-        setContentView(R.layout.media_route_chooser_dialog);
         setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY
                 ? R.string.media_route_chooser_title_for_remote_display
                 : R.string.media_route_chooser_title);
 
-        // Must be called after setContentView.
-        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
-                isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light
-                    : R.drawable.ic_media_route_off_holo_dark);
+        setIcon(isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light
+                : R.drawable.ic_media_route_off_holo_dark);
 
+        super.onCreate(savedInstanceState);
+
+        View emptyView = findViewById(android.R.id.empty);
         mAdapter = new RouteAdapter(getContext());
-        mListView = (ListView)findViewById(R.id.media_route_list);
+        mListView = (ListView) findViewById(R.id.media_route_list);
         mListView.setAdapter(mAdapter);
         mListView.setOnItemClickListener(mAdapter);
-        mListView.setEmptyView(findViewById(android.R.id.empty));
+        mListView.setEmptyView(emptyView);
 
-        mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button);
+        mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button);
         updateExtendedSettingsButton();
+
+        if (!mShowProgressBarWhenEmpty) {
+            findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE);
+
+            // Center the empty view when the progress bar is not shown.
+            LinearLayout.LayoutParams params =
+                    (LinearLayout.LayoutParams) emptyView.getLayoutParams();
+            params.gravity = Gravity.CENTER;
+            emptyView.setLayoutParams(params);
+        }
     }
 
     private void updateExtendedSettingsButton() {
diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
index bb2d7fa..5628b7e 100644
--- a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
+++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
@@ -66,24 +66,37 @@
         }
     }
 
+    /** Create a media route dialog as appropriate. */
     public static Dialog createDialog(Context context,
             int routeTypes, View.OnClickListener extendedSettingsClickListener) {
-        final MediaRouter router = (MediaRouter)context.getSystemService(
-                Context.MEDIA_ROUTER_SERVICE);
-
         int theme = MediaRouteChooserDialog.isLightTheme(context)
                 ? android.R.style.Theme_DeviceDefault_Light_Dialog
                 : android.R.style.Theme_DeviceDefault_Dialog;
+        return createDialog(context, routeTypes, extendedSettingsClickListener, theme);
+    }
+
+    /** Create a media route dialog as appropriate. */
+    public static Dialog createDialog(Context context,
+            int routeTypes, View.OnClickListener extendedSettingsClickListener, int theme) {
+        return createDialog(context, routeTypes, extendedSettingsClickListener, theme,
+                true /* showProgressBarWhenEmpty */);
+    }
+
+    /** Create a media route dialog as appropriate. */
+    public static Dialog createDialog(Context context, int routeTypes,
+            View.OnClickListener extendedSettingsClickListener, int theme,
+            boolean showProgressBarWhenEmpty) {
+        final MediaRouter router = context.getSystemService(MediaRouter.class);
 
         MediaRouter.RouteInfo route = router.getSelectedRoute();
         if (route.isDefault() || !route.matchesTypes(routeTypes)) {
-            final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme);
+            final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme,
+                    showProgressBarWhenEmpty);
             d.setRouteTypes(routeTypes);
             d.setExtendedSettingsClickListener(extendedSettingsClickListener);
             return d;
         } else {
-            MediaRouteControllerDialog d = new MediaRouteControllerDialog(context, theme);
-            return d;
+            return new MediaRouteControllerDialog(context, theme);
         }
     }
 }
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index d1942ac..780de0e 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -282,9 +282,8 @@
 
                     if (!alwaysTrueEndBatchEditDetected) {
                         final TextSnapshot textSnapshot = ic.takeSnapshot();
-                        if (textSnapshot != null) {
-                            mParentInputMethodManager.doInvalidateInput(this, textSnapshot,
-                                    nextSessionId);
+                        if (textSnapshot != null && mParentInputMethodManager.doInvalidateInput(
+                                this, textSnapshot, nextSessionId)) {
                             return;
                         }
                     }
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 9bdcddf..1fd0410 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -888,6 +888,15 @@
         }
     }
 
+    /**
+     * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code
+     * null}, otherwise returns {@code null}.
+     */
+    @Nullable
+    public static <T> T getOrNull(@Nullable T[] items, int i) {
+        return (items != null && items.length > i) ? items[i] : null;
+    }
+
     public static @Nullable <T> T firstOrNull(T[] items) {
         return items.length > 0 ? items[0] : null;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3b2a248..d4c03e4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4718,10 +4718,10 @@
     <permission android:name="android.permission.READ_FRAME_BUFFER"
         android:protectionLevel="signature|recents" />
 
-      <!-- @SystemApi Allows an application to change the touch mode state.
+      <!-- Allows an application to change the touch mode state.
            Without this permission, an app can only change the touch mode
            if it currently has focus.
-         @hide -->
+           @hide -->
     <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
         android:protectionLevel="signature" />
 
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
index cd1c74f..bf73f4b 100644
--- a/core/res/res/layout/media_route_chooser_dialog.xml
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -28,20 +28,21 @@
 
     <!-- Content to show when list is empty. -->
     <LinearLayout android:id="@android:id/empty"
-              android:layout_width="match_parent"
-              android:layout_height="64dp"
-              android:orientation="horizontal"
-              android:paddingLeft="16dp"
-              android:paddingRight="16dp"
-              android:visibility="gone">
-        <ProgressBar android:layout_width="wrap_content"
-                     android:layout_height="wrap_content"
-                     android:layout_gravity="center" />
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp"
+        android:visibility="gone">
+        <ProgressBar android:id="@+id/media_route_progress_bar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center" />
         <TextView android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="center"
-                  android:paddingStart="16dp"
-                  android:text="@string/media_route_chooser_searching" />
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:paddingStart="16dp"
+            android:text="@string/media_route_chooser_searching" />
     </LinearLayout>
 
     <!-- Settings button. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7150fca..689d37c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7013,30 +7013,42 @@
 
     <!-- Defines the ExtendAnimation used to extend windows during animations -->
     <declare-styleable name="ExtendAnimation">
-        <!-- Defines the amount a window should be extended outward from the left at
-             the start of the animation. -->
-        <attr name="fromExtendLeft" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the top at
-             the start of the animation. -->
-        <attr name="fromExtendTop" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the right at
-             the start of the animation. -->
-        <attr name="fromExtendRight" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the bottom at
-             the start of the animation. -->
-        <attr name="fromExtendBottom" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the left by
-             the end of the animation by transitioning from the fromExtendLeft amount. -->
-        <attr name="toExtendLeft" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the top by
-             the end of the animation by transitioning from the fromExtendTop amount. -->
-        <attr name="toExtendTop" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the right by
-             the end of the animation by transitioning from the fromExtendRight amount. -->
-        <attr name="toExtendRight" format="float|fraction" />
-        <!-- Defines the amount a window should be extended outward from the bottom by
-             the end of the animation by transitioning from the fromExtendBottom amount. -->
-        <attr name="toExtendBottom" format="float|fraction" />
+        <!-- Defines the amount a window should be extended outward from the left at the start of
+             the animation in an absolute dimension (interpreted as pixels if no dimension unit is
+             provided) or as a percentage of the animation target's width. -->
+        <attr name="fromExtendLeft" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the top at the start of
+             the animation in an absolute dimension (interpreted as pixels if no dimension unit is
+             provided) or as a percentage of the animation target's height. -->
+        <attr name="fromExtendTop" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the right at the start of
+             the animation in an absolute dimension (interpreted as pixels if no dimension unit is
+             provided) or as a percentage of the animation target's width. -->
+        <attr name="fromExtendRight" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the bottom at the start of
+             the animation in an absolute dimension (interpreted as pixels if no dimension unit is
+             provided) or as a percentage of the animation target's height. -->
+        <attr name="fromExtendBottom" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the left by the end of the
+             animation by transitioning from the fromExtendLeft amount in an absolute dimension
+             (interpreted as pixels if no dimension unit is provided) or as a percentage of the
+             animation target's width. -->
+        <attr name="toExtendLeft" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the top by the end of the
+             animation by transitioning from the fromExtendTop amount in an absolute dimension
+             (interpreted as pixels if no dimension unit is provided) or as a percentage of the
+             animation target's height. -->
+        <attr name="toExtendTop" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the right by the end of
+             the animation by transitioning from the fromExtendRight amount in an absolute
+             dimension (interpreted as pixels if no dimension unit is provided) or as a percentage
+             of the animation target's width. -->
+        <attr name="toExtendRight" format="float|fraction|dimension" />
+        <!-- Defines the amount a window should be extended outward from the bottom by the end of
+             the animation by transitioning from the fromExtendBottom amount in an absolute
+             dimension (interpreted as pixels if no dimension unit is provided) or as a percentage
+             of the animation target's height. -->
+        <attr name="toExtendBottom" format="float|fraction|dimension" />
     </declare-styleable>
 
     <declare-styleable name="LayoutAnimation">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e7eeecc..d4513d0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1676,6 +1676,7 @@
   <java-symbol type="id" name="media_route_volume_slider" />
   <java-symbol type="id" name="media_route_control_frame" />
   <java-symbol type="id" name="media_route_extended_settings_button" />
+  <java-symbol type="id" name="media_route_progress_bar" />
   <java-symbol type="string" name="media_route_chooser_title" />
   <java-symbol type="string" name="media_route_chooser_title_for_remote_display" />
   <java-symbol type="string" name="media_route_controller_disconnect" />
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2f978fc..582488f 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -22,6 +22,10 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.SuppressAutoDoc;
+import android.annotation.SuppressLint;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
+import android.util.SparseIntArray;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -207,6 +211,7 @@
 
     // See static initialization block next to #get(Named)
     private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+    private static final SparseIntArray sDataToColorSpaces = new SparseIntArray();
 
     @NonNull private final String mName;
     @NonNull private final Model mModel;
@@ -1389,6 +1394,47 @@
     }
 
     /**
+     * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace}
+     * value.
+     *
+     * <p>This function maps from a dataspace to a {@link Named} ColorSpace.
+     * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created,
+     * {@code null} will return.</p>
+     *
+     * @param dataSpace The dataspace value
+     * @return the ColorSpace object or {@code null} if no matching colorspace can be found.
+     */
+    @SuppressLint("MethodNameUnits")
+    @Nullable
+    public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+        int index = sDataToColorSpaces.get(dataSpace, -1);
+        if (index != -1) {
+            return ColorSpace.get(index);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace}
+     * object.
+     *
+     * <p>If this {@link ColorSpace} object has no matching {@code dataSpace} value,
+     * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.</p>
+     *
+     * @return the dataspace value.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace int getDataSpace() {
+        int index = sDataToColorSpaces.indexOfValue(getId());
+        if (index != -1) {
+            return sDataToColorSpaces.keyAt(index);
+        } else {
+            return DataSpace.DATASPACE_UNKNOWN;
+        }
+    }
+
+    /**
      * <p>Returns an instance of {@link ColorSpace} identified by the specified
      * name. The list of names provided in the {@link Named} enum gives access
      * to a variety of common RGB color spaces.</p>
@@ -1445,6 +1491,7 @@
                 SRGB_TRANSFER_PARAMETERS,
                 Named.SRGB.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal());
         sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
                 "sRGB IEC61966-2.1 (Linear)",
                 SRGB_PRIMARIES,
@@ -1453,6 +1500,7 @@
                 0.0f, 1.0f,
                 Named.LINEAR_SRGB.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal());
         sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
                 "scRGB-nl IEC 61966-2-2:2003",
                 SRGB_PRIMARIES,
@@ -1464,6 +1512,7 @@
                 SRGB_TRANSFER_PARAMETERS,
                 Named.EXTENDED_SRGB.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal());
         sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
                 "scRGB IEC 61966-2-2:2003",
                 SRGB_PRIMARIES,
@@ -1472,6 +1521,8 @@
                 -0.5f, 7.499f,
                 Named.LINEAR_EXTENDED_SRGB.ordinal()
         );
+        sDataToColorSpaces.put(
+                DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
         sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
                 "Rec. ITU-R BT.709-5",
                 new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
@@ -1480,6 +1531,7 @@
                 new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
                 Named.BT709.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
         sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
                 "Rec. ITU-R BT.2020-1",
                 new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
@@ -1488,6 +1540,7 @@
                 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
                 Named.BT2020.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
         sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
                 "SMPTE RP 431-2-2007 DCI (P3)",
                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1496,6 +1549,7 @@
                 0.0f, 1.0f,
                 Named.DCI_P3.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
         sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
                 "Display P3",
                 new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
@@ -1504,6 +1558,7 @@
                 SRGB_TRANSFER_PARAMETERS,
                 Named.DISPLAY_P3.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal());
         sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
                 "NTSC (1953)",
                 NTSC_1953_PRIMARIES,
@@ -1528,6 +1583,7 @@
                 0.0f, 1.0f,
                 Named.ADOBE_RGB.ordinal()
         );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal());
         sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
                 "ROMM RGB ISO 22028-2:2013",
                 new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 9feb619..b341a4e 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -428,7 +428,7 @@
 
     /**
      * <p>Private raw camera sensor image format, a single channel image with
-     * implementation depedent pixel layout.</p>
+     * implementation dependent pixel layout.</p>
      *
      * <p>RAW_PRIVATE is a format for unprocessed raw image buffers coming from an
      * image sensor. The actual structure of buffers of this format is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 101295d..11ecc91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,6 +35,9 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -54,6 +57,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -205,6 +209,7 @@
                 break;
             case ACTION_DRAG_ENTERED:
                 pd.dragLayout.show();
+                pd.dragLayout.update(event);
                 break;
             case ACTION_DRAG_LOCATION:
                 pd.dragLayout.update(event);
@@ -250,10 +255,6 @@
                 // Hide the window if another drag hasn't been started while animating the drop
                 setDropTargetWindowVisibility(pd, View.INVISIBLE);
             }
-
-            // Clean up the drag surface
-            mTransaction.reparent(dragSurface, null);
-            mTransaction.apply();
         });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index e8bae0f..7568310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -123,6 +123,13 @@
     }
 
     /**
+     * Returns the number of targets.
+     */
+    int getNumTargets() {
+        return mTargets.size();
+    }
+
+    /**
      * Returns the target's regions based on the current state of the device and display.
      */
     @NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index d395f95..25fe8b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.draganddrop;
 
 import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -24,6 +25,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
@@ -44,6 +46,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -135,6 +138,12 @@
         }
     }
 
+    private void updateContainerMarginsForSingleTask() {
+        mDropZoneView1.setContainerMargin(
+                mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
+        mDropZoneView2.setContainerMargin(0, 0, 0, 0);
+    }
+
     private void updateContainerMargins(int orientation) {
         final float halfMargin = mDisplayMargin / 2f;
         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -165,11 +174,20 @@
         if (!alreadyInSplit) {
             ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
             if (taskInfo1 != null) {
-                Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
-                int bgColor1 = getResizingBackgroundColor(taskInfo1);
-                mDropZoneView1.setAppInfo(bgColor1, icon1);
-                mDropZoneView2.setAppInfo(bgColor1, icon1);
-                updateDropZoneSizes(null, null); // passing null splits the views evenly
+                final int activityType = taskInfo1.getActivityType();
+                if (activityType == ACTIVITY_TYPE_STANDARD) {
+                    Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
+                    int bgColor1 = getResizingBackgroundColor(taskInfo1);
+                    mDropZoneView1.setAppInfo(bgColor1, icon1);
+                    mDropZoneView2.setAppInfo(bgColor1, icon1);
+                    updateDropZoneSizes(null, null); // passing null splits the views evenly
+                } else {
+                    // We use the first drop zone to show the fullscreen highlight, and don't need
+                    // to set additional info
+                    mDropZoneView1.setForceIgnoreBottomMargin(true);
+                    updateDropZoneSizesForSingleTask();
+                    updateContainerMarginsForSingleTask();
+                }
             }
         } else {
             // We're already in split so get taskInfo from the controller to populate icon / color.
@@ -195,6 +213,21 @@
         }
     }
 
+    private void updateDropZoneSizesForSingleTask() {
+        final LinearLayout.LayoutParams dropZoneView1 =
+                (LayoutParams) mDropZoneView1.getLayoutParams();
+        final LinearLayout.LayoutParams dropZoneView2 =
+                (LayoutParams) mDropZoneView2.getLayoutParams();
+        dropZoneView1.width = MATCH_PARENT;
+        dropZoneView1.height = MATCH_PARENT;
+        dropZoneView2.width = 0;
+        dropZoneView2.height = 0;
+        dropZoneView1.weight = 1;
+        dropZoneView2.weight = 0;
+        mDropZoneView1.setLayoutParams(dropZoneView1);
+        mDropZoneView2.setLayoutParams(dropZoneView2);
+    }
+
     /**
      * Sets the size of the two drop zones based on the provided bounds. The divider sits between
      * the views and its size is included in the calculations.
@@ -265,9 +298,12 @@
                 // Animating to no target
                 animateSplitContainers(false, null /* animCompleteCallback */);
             } else if (mCurrentTarget == null) {
-                // Animating to first target
-                animateSplitContainers(true, null /* animCompleteCallback */);
-                animateHighlight(target);
+                if (mPolicy.getNumTargets() == 1) {
+                    animateFullscreenContainer(true);
+                } else {
+                    animateSplitContainers(true, null /* animCompleteCallback */);
+                    animateHighlight(target);
+                }
             } else {
                 // Switching between targets
                 mDropZoneView1.animateSwitch();
@@ -283,6 +319,10 @@
     public void hide(DragEvent event, Runnable hideCompleteCallback) {
         mIsShowing = false;
         animateSplitContainers(false, hideCompleteCallback);
+        // Reset the state if we previously force-ignore the bottom margin
+        mDropZoneView1.setForceIgnoreBottomMargin(false);
+        mDropZoneView2.setForceIgnoreBottomMargin(false);
+        updateContainerMargins(getResources().getConfiguration().orientation);
         mCurrentTarget = null;
     }
 
@@ -297,11 +337,63 @@
         // Process the drop
         mPolicy.handleDrop(mCurrentTarget, event.getClipData());
 
-        // TODO(b/169894807): Coordinate with dragSurface
+        // Start animating the drop UI out with the drag surface
         hide(event, dropCompleteCallback);
+        hideDragSurface(dragSurface);
         return handledDrop;
     }
 
+    private void hideDragSurface(SurfaceControl dragSurface) {
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f);
+        // Currently the splash icon animation runs with the default ValueAnimator duration of
+        // 300ms
+        dragSurfaceAnimator.setDuration(300);
+        dragSurfaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        dragSurfaceAnimator.addUpdateListener(animation -> {
+            float t = animation.getAnimatedFraction();
+            float alpha = 1f - t;
+            // TODO: Scale the drag surface as well once we make all the source surfaces
+            //       consistent
+            tx.setAlpha(dragSurface, alpha);
+            tx.apply();
+        });
+        dragSurfaceAnimator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCanceled = false;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                cleanUpSurface();
+                mCanceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCanceled) {
+                    // Already handled above
+                    return;
+                }
+                cleanUpSurface();
+            }
+
+            private void cleanUpSurface() {
+                // Clean up the drag surface
+                tx.remove(dragSurface);
+                tx.apply();
+            }
+        });
+        dragSurfaceAnimator.start();
+    }
+
+    private void animateFullscreenContainer(boolean visible) {
+        mStatusBarManager.disable(visible
+                ? HIDE_STATUS_BAR_FLAGS
+                : DISABLE_NONE);
+        // We're only using the first drop zone if there is one fullscreen target
+        mDropZoneView1.setShowingMargin(visible);
+        mDropZoneView1.setShowingHighlight(visible);
+    }
+
     private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
         mStatusBarManager.disable(visible
                 ? HIDE_STATUS_BAR_FLAGS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index a3ee8ae..38870bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -65,6 +65,7 @@
     private final float[] mContainerMargin = new float[4];
     private float mCornerRadius;
     private float mBottomInset;
+    private boolean mIgnoreBottomMargin;
     private int mMarginColor; // i.e. color used for negative space like the container insets
 
     private boolean mShowingHighlight;
@@ -141,6 +142,14 @@
         }
     }
 
+    /** Ignores the bottom margin provided by the insets. */
+    public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) {
+        mIgnoreBottomMargin = ignoreBottomMargin;
+        if (mMarginPercent > 0) {
+            mMarginView.invalidate();
+        }
+    }
+
     /** Sets the bottom inset so the drop zones are above bottom navigation. */
     public void setBottomInset(float bottom) {
         mBottomInset = bottom;
@@ -257,7 +266,8 @@
             mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
                     mContainerMargin[1] * mMarginPercent,
                     getWidth() - (mContainerMargin[2] * mMarginPercent),
-                    getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset,
+                    getHeight() - (mContainerMargin[3] * mMarginPercent)
+                            - (mIgnoreBottomMargin ? 0 : mBottomInset),
                     mCornerRadius * mMarginPercent,
                     mCornerRadius * mMarginPercent,
                     Path.Direction.CW);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 5996acd..5069180 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -289,7 +290,6 @@
         if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask);
         if (pinnedTask == null || pinnedTask.topActivity == null) return;
         mPinnedTaskId = pinnedTask.taskId;
-        setState(STATE_PIP);
 
         mPipMediaController.onActivityPinned();
         mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
@@ -326,6 +326,9 @@
 
     @Override
     public void onPipTransitionFinished(int direction) {
+        if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) {
+            setState(STATE_PIP);
+        }
         if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index c22d3f6..0b4bc76 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -93,15 +93,4 @@
                 .isVisible(LAUNCHER_COMPONENT)
         }
     }
-
-    /**
-     * Checks that the focus doesn't change between windows during the transition
-     */
-    @Presubmit
-    @Test
-    open fun focusDoesNotChange() {
-        testSpec.assertEventLog {
-            this.focusDoesNotChange()
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 5214daa0..9c095a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -72,10 +73,17 @@
     @Test
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
-    /** {@inheritDoc}  */
-    @FlakyTest(bugId = 215869110)
+    /**
+     * Checks that the focus changes between the pip menu window and the launcher when clicking the
+     * dismiss button on pip menu to close the pip window.
+     */
+    @Presubmit
     @Test
-    override fun focusDoesNotChange() = super.focusDoesNotChange()
+    fun focusDoesNotChange() {
+        testSpec.assertEventLog {
+            this.focusChanges("PipMenuView", "NexusLauncherActivity")
+        }
+    }
 
     companion object {
         /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 332bba6a..ab07ede 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -81,6 +82,17 @@
     @Test
     override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
 
+    /**
+     * Checks that the focus doesn't change between windows during the transition
+     */
+    @Presubmit
+    @Test
+    fun focusDoesNotChange() {
+        testSpec.assertEventLog {
+            this.focusDoesNotChange()
+        }
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 96809bd..69fe5ee 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -1863,12 +1863,9 @@
      * Returns a priority for the given use case type and the client's foreground or background
      * status.
      *
-     * @param useCase the use case type of the client. When the given use case type is invalid,
-     *        the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}.
-     * @param sessionId the unique id of the session owned by the client. When {@code null},
-     *        the caller will be used as a client. When the session is invalid, background status
-     *        will be used as a client's status. Otherwise, TV app corresponding to the given
-     *        session id will be used as a client.
+     * @param useCase the use case type of the client.
+     *        {@see TvInputService#PriorityHintUseCaseType}.
+     * @param sessionId the unique id of the session owned by the client.
      *        {@see TvInputService#onCreateSession(String, String)}.
      *
      * @return the use case priority value for the given use case type and the client's foreground
@@ -1879,11 +1876,35 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
     public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase,
-            @Nullable String sessionId) {
+            @NonNull String sessionId) {
+        Preconditions.checkNotNull(sessionId);
+        if (!isValidUseCase(useCase)) {
+            throw new IllegalArgumentException("Invalid use case: " + useCase);
+        }
         return getClientPriorityInternal(useCase, sessionId);
     };
 
     /**
+     * Returns a priority for the given use case type and the caller's foreground or background
+     * status.
+     *
+     * @param useCase the use case type of the caller.
+     *        {@see TvInputService#PriorityHintUseCaseType}.
+     *
+     * @return the use case priority value for the given use case type and the caller's foreground
+     *         or background status.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase) {
+        if (!isValidUseCase(useCase)) {
+            throw new IllegalArgumentException("Invalid use case: " + useCase);
+        }
+        return getClientPriorityInternal(useCase, null);
+    };
+    /**
      * Creates a recording {@link Session} for a given TV input.
      *
      * <p>The number of sessions that can be created at the same time is limited by the capability
@@ -1935,6 +1956,14 @@
         }
     }
 
+    private boolean isValidUseCase(int useCase) {
+        return useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND
+            || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN
+            || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK
+            || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE
+            || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD;
+    }
+
     /**
      * Returns the TvStreamConfig list of the given TV input.
      *
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 9ed1ac0..397c704 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1839,7 +1839,7 @@
     if (mDemuxClient == nullptr) {
         return nullptr;
     }
-    long time = mDemuxClient->getAvSyncTime((int)id);
+    int64_t time = mDemuxClient->getAvSyncTime((int)id);
     if (time >= 0) {
         JNIEnv *env = AndroidRuntime::getJNIEnv();
         jclass longClazz = env->FindClass("java/lang/Long");
@@ -4592,7 +4592,8 @@
         ALOGD("Failed to GetByteArrayElements");
         return -1;
     }
-    long realSize = dvrClient->readFromBuffer(reinterpret_cast<signed char *>(src) + offset, size);
+    int64_t realSize =
+            dvrClient->readFromBuffer(reinterpret_cast<signed char *>(src) + offset, size);
     env->ReleaseByteArrayElements(buffer, src, 0);
     return (jlong)realSize;
 }
@@ -4624,7 +4625,8 @@
         return -1;
     }
 
-    long realSize = dvrClient->writeToBuffer(reinterpret_cast<signed char *>(dst) + offset, size);
+    int64_t realSize =
+            dvrClient->writeToBuffer(reinterpret_cast<signed char *>(dst) + offset, size);
     env->ReleaseByteArrayElements(buffer, dst, 0);
     return (jlong)realSize;
 }
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 6ded1637..9f5bfd4 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -34,6 +34,7 @@
 android_app {
     name: "CompanionDeviceManager",
     defaults: ["platform_app_defaults"],
+    certificate: "platform",
     srcs: ["src/**/*.java"],
 
     static_libs: [
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 06f2d9d..8b5d214 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application
         android:allowClearUserData="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index b51d310..a6a8fcf 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -37,6 +37,7 @@
 import android.companion.CompanionDeviceManager;
 import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.MacAddress;
 import android.os.Bundle;
 import android.os.Handler;
@@ -71,6 +72,9 @@
     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
     private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
 
+    // Activity result: Internal Error.
+    private static final int RESULT_INTERNAL_ERROR = 2;
+
     // AssociationRequestsProcessor -> UI
     private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
     private static final String EXTRA_ASSOCIATION = "association";
@@ -191,6 +195,20 @@
     private void initUI() {
         if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
 
+        final String packageName = mRequest.getPackageName();
+        final int userId = mRequest.getUserId();
+        final CharSequence appLabel;
+
+        try {
+            appLabel = getApplicationLabel(this, packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Package u" + userId + "/" + packageName + " not found.");
+
+            CompanionDeviceDiscoveryService.stop(this);
+            setResultAndFinish(null, RESULT_INTERNAL_ERROR);
+            return;
+        }
+
         setContentView(R.layout.activity_confirmation);
 
         mTitle = findViewById(R.id.title);
@@ -203,8 +221,6 @@
         mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
         findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
 
-        final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
-
         if (mRequest.isSelfManaged()) {
             initUiForSelfManagedAssociation(appLabel);
         } else if (mRequest.isSingleDevice()) {
@@ -257,7 +273,7 @@
         if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
 
         // Don't need to notify the app, CdmService has already done that. Just finish.
-        setResultAndFinish(association);
+        setResultAndFinish(association, RESULT_OK);
     }
 
     private void cancel(boolean discoveryTimeout) {
@@ -284,10 +300,10 @@
         }
 
         // ... then set result and finish ("sending" onActivityResult()).
-        setResultAndFinish(null);
+        setResultAndFinish(null, RESULT_CANCELED);
     }
 
-    private void setResultAndFinish(@Nullable AssociationInfo association) {
+    private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
         if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association);
 
         final Intent data = new Intent();
@@ -297,7 +313,7 @@
                 data.putExtra(CompanionDeviceManager.EXTRA_DEVICE, mSelectedDevice.getDevice());
             }
         }
-        setResult(association != null ? RESULT_OK : RESULT_CANCELED, data);
+        setResult(resultCode, data);
 
         finish();
     }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index eab421e..e3e563d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -50,14 +50,13 @@
     }
 
     static @NonNull CharSequence getApplicationLabel(
-            @NonNull Context context, @NonNull String packageName) {
+            @NonNull Context context, @NonNull String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
         final PackageManager packageManager = context.getPackageManager();
-        final ApplicationInfo appInfo;
-        try {
-            appInfo = packageManager.getApplicationInfo(packageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new RuntimeException(e);
-        }
+
+        final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+                packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
+
         return packageManager.getApplicationLabel(appInfo);
     }
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index 9175809..f681ba1 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -500,7 +501,7 @@
                         && roaming == e.roaming && defaultNetwork == e.defaultNetwork
                         && rxBytes == e.rxBytes && rxPackets == e.rxPackets
                         && txBytes == e.txBytes && txPackets == e.txPackets
-                        && operations == e.operations && iface.equals(e.iface);
+                        && operations == e.operations && TextUtils.equals(iface, e.iface);
             }
             return false;
         }
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index ef9ebb5..ef6f39a 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -1887,7 +1887,13 @@
     private void deleteKernelTagData(int uid) {
         try {
             mCookieTagMap.forEach((key, value) -> {
-                if (value.uid == uid) {
+                // If SkDestroyListener deletes the socket tag while this code is running,
+                // forEach will either restart iteration from the beginning or return null,
+                // depending on when the deletion happens.
+                // If it returns null, continue iteration to delete the data and in fact it would
+                // just iterate from first key because BpfMap#getNextKey would return first key
+                // if the current key is not exist.
+                if (value != null && value.uid == uid) {
                     try {
                         mCookieTagMap.deleteEntry(key);
                     } catch (ErrnoException e) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
index 72230b4..4117d0f 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
@@ -177,7 +177,7 @@
                 ret = 0;
                 break;
             case SparseChunk.FILL:
-                ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3];
+                ret = Byte.toUnsignedInt(mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3]);
                 break;
             default:
                 throw new IOException("Unsupported Chunk:" + mCur.toString());
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8965144e..014a033 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1160,6 +1160,8 @@
     <string name="battery_info_status_not_charging">Connected, not charging</string>
     <!-- Battery Info screen. Value for a status item.  Used for diagnostic info screens, precise translation isn't needed -->
     <string name="battery_info_status_full">Charged</string>
+    <!-- [CHAR_LIMIT=40] Battery Info screen. Value for a status item. A state which device is fully charged -->
+    <string name="battery_info_status_full_charged">Fully Charged</string>
 
     <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
     <string name="disabled_by_admin_summary_text">Controlled by admin</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f6e3557..19114cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -194,9 +194,11 @@
      * @param context the context
      * @param batteryChangedIntent battery broadcast intent received from {@link
      *                             Intent.ACTION_BATTERY_CHANGED}.
+     * @param compactStatus to present compact battery charging string if {@code true}
      * @return battery status string
      */
-    public static String getBatteryStatus(Context context, Intent batteryChangedIntent) {
+    public static String getBatteryStatus(Context context, Intent batteryChangedIntent,
+            boolean compactStatus) {
         final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
                 BatteryManager.BATTERY_STATUS_UNKNOWN);
         final Resources res = context.getResources();
@@ -205,10 +207,14 @@
         final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
 
         if (batteryStatus.isCharged()) {
-            statusString = res.getString(R.string.battery_info_status_full);
+            statusString = res.getString(compactStatus
+                    ? R.string.battery_info_status_full_charged
+                    : R.string.battery_info_status_full);
         } else {
             if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
-                if (batteryStatus.isPluggedInWired()) {
+                if (compactStatus) {
+                    statusString = res.getString(R.string.battery_info_status_charging);
+                } else if (batteryStatus.isPluggedInWired()) {
                     switch (batteryStatus.getChargingSpeed(context)) {
                         case BatteryStatus.CHARGING_FAST:
                             statusString = res.getString(
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
new file mode 100644
index 0000000..8aee576
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.provider.Settings;
+
+/**
+ * Implementation of {@link SecureSettings} that uses Android's {@link Settings.Secure}
+ * implementation.
+ */
+class AndroidSecureSettings implements SecureSettings {
+
+    private final ContentResolver mContentResolver;
+
+    AndroidSecureSettings(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    @Override
+    public void putStringForUser(String name, String value, int userHandle) {
+        Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle);
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return Settings.Secure.getStringForUser(mContentResolver, name, userHandle);
+    }
+
+    @Override
+    public void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver observer, int userHandle) {
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(name),
+                notifyForDescendants,
+                observer,
+                userHandle);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index afd3626..961fab3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -47,28 +48,33 @@
 
     private static DeviceStateRotationLockSettingsManager sSingleton;
 
-    private final ContentResolver mContentResolver;
     private final String[] mDeviceStateRotationLockDefaults;
-    private final Handler mMainHandler = Handler.getMain();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
     private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
+    private final SecureSettings mSecureSettings;
     private SparseIntArray mDeviceStateRotationLockSettings;
     private SparseIntArray mDeviceStateRotationLockFallbackSettings;
+    private String mLastSettingValue;
 
-    private DeviceStateRotationLockSettingsManager(Context context) {
-        mContentResolver = context.getContentResolver();
+    @VisibleForTesting
+    DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
+        this.mSecureSettings = secureSettings;
         mDeviceStateRotationLockDefaults =
                 context.getResources()
                         .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
         loadDefaults();
         initializeInMemoryMap();
-        listenForSettingsChange(context);
+        listenForSettingsChange();
     }
 
     /** Returns a singleton instance of this class */
     public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
         if (sSingleton == null) {
+            Context applicationContext = context.getApplicationContext();
+            ContentResolver contentResolver = applicationContext.getContentResolver();
+            SecureSettings secureSettings = new AndroidSecureSettings(contentResolver);
             sSingleton =
-                    new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
+                    new DeviceStateRotationLockSettingsManager(applicationContext, secureSettings);
         }
         return sSingleton;
     }
@@ -81,11 +87,11 @@
                 > 0;
     }
 
-    private void listenForSettingsChange(Context context) {
-        context.getContentResolver()
+    private void listenForSettingsChange() {
+        mSecureSettings
                 .registerContentObserver(
-                        Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
-                        /* notifyForDescendents= */ false, //NOTYPO
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        /* notifyForDescendants= */ false,
                         new ContentObserver(mMainHandler) {
                             @Override
                             public void onChange(boolean selfChange) {
@@ -182,8 +188,7 @@
 
     private void initializeInMemoryMap() {
         String serializedSetting =
-                Settings.Secure.getStringForUser(
-                        mContentResolver,
+                mSecureSettings.getStringForUser(
                         Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                         UserHandle.USER_CURRENT);
         if (TextUtils.isEmpty(serializedSetting)) {
@@ -222,11 +227,7 @@
 
     private void persistSettings() {
         if (mDeviceStateRotationLockSettings.size() == 0) {
-            Settings.Secure.putStringForUser(
-                    mContentResolver,
-                    Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                    /* value= */ "",
-                    UserHandle.USER_CURRENT);
+            persistSettingIfChanged(/* newSettingValue= */ "");
             return;
         }
 
@@ -243,10 +244,17 @@
                     .append(SEPARATOR_REGEX)
                     .append(mDeviceStateRotationLockSettings.valueAt(i));
         }
-        Settings.Secure.putStringForUser(
-                mContentResolver,
+        persistSettingIfChanged(stringBuilder.toString());
+    }
+
+    private void persistSettingIfChanged(String newSettingValue) {
+        if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
+            return;
+        }
+        mLastSettingValue = newSettingValue;
+        mSecureSettings.putStringForUser(
                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
-                stringBuilder.toString(),
+                /* value= */ newSettingValue,
                 UserHandle.USER_CURRENT);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
new file mode 100644
index 0000000..1052873
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.database.ContentObserver;
+
+/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
+interface SecureSettings {
+
+    void putStringForUser(String name, String value, int userHandle);
+
+    String getStringForUser(String name, int userHandle);
+
+    void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver, int userHandle);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 93be66a..1e1dfae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -81,6 +81,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setTheme(R.style.SudThemeGlifV3_DayNight);
         ThemeHelper.trySetDynamicColor(this);
         setContentView(R.layout.avatar_picker);
         setUpButtons();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
new file mode 100644
index 0000000..1a45384
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateRotationLockSettingsManagerTest {
+
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+
+    private DeviceStateRotationLockSettingsManager mManager;
+    private int mNumSettingsChanges = 0;
+    private final ContentObserver mContentObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean selfChange) {
+            mNumSettingsChanges++;
+        }
+    };
+    private final FakeSecureSettings mFakeSecureSettings = new FakeSecureSettings();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getTargetContext();
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
+        mFakeSecureSettings.registerContentObserver(
+                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                /* notifyForDescendents= */ false, //NOTYPO
+                mContentObserver,
+                UserHandle.USER_CURRENT);
+        mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings);
+    }
+
+    @Test
+    public void initialization_settingsAreChangedOnce() {
+        assertThat(mNumSettingsChanges).isEqualTo(1);
+    }
+
+    @Test
+    public void updateSetting_multipleTimes_sameValue_settingsAreChangedOnlyOnce() {
+        mNumSettingsChanges = 0;
+
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+        assertThat(mNumSettingsChanges).isEqualTo(1);
+    }
+
+    @Test
+    public void updateSetting_multipleTimes_differentValues_settingsAreChangedMultipleTimes() {
+        mNumSettingsChanges = 0;
+
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ false);
+        mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true);
+
+        assertThat(mNumSettingsChanges).isEqualTo(3);
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
new file mode 100644
index 0000000..91baa68
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.devicestate;
+
+import android.database.ContentObserver;
+import android.util.Pair;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Fake implementation of {@link SecureSettings} that stores everything in memory. */
+class FakeSecureSettings implements SecureSettings {
+
+    private final Map<SettingsKey, String> mValues = new HashMap<>();
+    private final Multimap<SettingsKey, ContentObserver> mContentObservers = HashMultimap.create();
+
+    @Override
+    public void putStringForUser(String name, String value, int userHandle) {
+        SettingsKey settingsKey = new SettingsKey(userHandle, name);
+        mValues.put(settingsKey, value);
+        for (ContentObserver observer : mContentObservers.get(settingsKey)) {
+            observer.onChange(/* selfChange= */ false);
+        }
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return mValues.getOrDefault(new SettingsKey(userHandle, name), "");
+    }
+
+    @Override
+    public void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver, int userHandle) {
+        mContentObservers.put(new SettingsKey(userHandle, name), settingsObserver);
+    }
+
+    private static class SettingsKey extends Pair<Integer, String> {
+
+        SettingsKey(Integer userHandle, String settingName) {
+            super(userHandle, settingName);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
new file mode 100644
index 0000000..98f4123
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS
@@ -0,0 +1,3 @@
+# Default reviewers for this and subdirectories.
+alexflo@google.com
+chrisgollner@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 62de66e..09b2a2e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -320,28 +320,47 @@
         final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
                 resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
+    public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
+                resources.getString(R.string.battery_info_status_full_charged));
+    }
+
+    @Test
     public void getBatteryStatus_batteryLevelIs100_returnFullString() {
         final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
                 BatteryManager.BATTERY_STATUS_FULL);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
                 resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
+    public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
+        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
+                BatteryManager.BATTERY_STATUS_FULL);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
+                resources.getString(R.string.battery_info_status_full_charged));
+    }
+
+    @Test
     public void getBatteryStatus_batteryLevel99_returnChargingString() {
         final Intent intent = new Intent();
         intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
                 resources.getString(R.string.battery_info_status_charging));
     }
 
@@ -352,7 +371,29 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo(
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
                 resources.getString(R.string.battery_info_status_charging_wireless));
     }
+
+    @Test
+    public void getBatteryStatus_chargingAndUseCompactStatus_returnCompactString() {
+        final Intent intent = new Intent();
+        intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+        intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
+                resources.getString(R.string.battery_info_status_charging));
+    }
+
+    @Test
+    public void getBatteryStatus_chargingWirelessAndUseCompactStatus_returnCompactString() {
+        final Intent intent = new Intent();
+        intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+        intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
+        final Resources resources = mContext.getResources();
+
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
+                resources.getString(R.string.battery_info_status_charging));
+    }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index c3f6a5d..0da60f0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -298,6 +298,9 @@
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
              * during the animation. For safety, this method will return null when it is not.
+             *
+             * Note: The background of [view] should be a (rounded) rectangle so that it can be
+             * properly animated.
              */
             @JvmStatic
             fun fromView(view: View, cujType: Int? = null): Controller? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index a3c5649..50178f4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -79,6 +79,9 @@
      * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be
      * animated when the dialog bounds change.
      *
+     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+     * animated.
+     *
      * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be
      * made fullscreen and 2 views will be inserted between the dialog DecorView and its children.
      */
@@ -153,6 +156,9 @@
      * activity started, when the dialog to app animation is done (or when it is cancelled). If this
      * method returns null, then the dialog won't be dismissed.
      *
+     * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+     * animated.
+     *
      * @param view any view inside the dialog to animate.
      */
     @JvmOverloads
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
new file mode 100644
index 0000000..0544b871
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white"/>
+            <corners android:radius="18dp"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="18dp"/>
+            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index d057f5f..31a8c3b 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -19,13 +19,17 @@
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
-            <shape android:shape="oval">
+            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+            <!-- properly into an app/dialog. -->
+            <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
         <item>
-            <shape android:shape="oval">
+            <shape android:shape="rectangle">
                 <solid android:color="?attr/offStateColor"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
 
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 944061c..021a85f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -19,13 +19,17 @@
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
-            <shape android:shape="oval">
+            <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
+            <!-- properly into an app/dialog. -->
+            <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
         <item>
-            <shape android:shape="oval">
+            <shape android:shape="rectangle">
                 <solid android:color="?android:attr/colorAccent"/>
+                <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
 
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
index a3e289a..e06bfdc 100644
--- a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -22,10 +22,6 @@
     android:scrollbarAlwaysDrawVerticalTrack="true"
     android:scrollIndicators="top|bottom"
     android:fillViewport="true"
-    android:paddingTop="@dimen/dialog_button_bar_top_padding"
-    android:paddingStart="@dimen/dialog_side_padding"
-    android:paddingEnd="@dimen/dialog_side_padding"
-    android:paddingBottom="@dimen/dialog_bottom_padding"
     style="?android:attr/buttonBarStyle">
     <com.android.internal.widget.ButtonBarLayout
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index f280cbd..ca8fadd 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -83,9 +83,15 @@
             android:layout_height="wrap_content" />
     </FrameLayout>
 
-    <include
+    <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        layout="@layout/alert_dialog_button_bar_systemui" />
+        android:paddingStart="@dimen/dialog_side_padding"
+        android:paddingEnd="@dimen/dialog_side_padding">
+        <include
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            layout="@layout/alert_dialog_button_bar_systemui" />
+    </FrameLayout>
 
 </com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 330f515..8e83b4a 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -34,34 +34,5 @@
         app:layout_constraintBottom_toBottomOf="parent"
         />
 
-    <com.android.systemui.dreams.DreamOverlayStatusBarView
-        android:id="@+id/dream_overlay_status_bar"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/dream_overlay_status_bar_height"
-        android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
-        android:paddingStart="@dimen/dream_overlay_status_bar_margin"
-        app:layout_constraintTop_toTopOf="parent">
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/dream_overlay_system_status"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            app:layout_constraintEnd_toEndOf="parent">
-
-            <com.android.systemui.statusbar.AlphaOptimizedImageView
-                android:id="@+id/dream_overlay_wifi_status"
-                android:layout_width="@dimen/status_bar_wifi_signal_size"
-                android:layout_height="match_parent"
-                android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
-                android:visibility="gone"
-                app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
-
-            <com.android.systemui.battery.BatteryMeterView
-                android:id="@+id/dream_overlay_battery"
-                android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                app:layout_constraintEnd_toEndOf="parent" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </com.android.systemui.dreams.DreamOverlayStatusBarView>
+    <include layout="@layout/dream_overlay_status_bar_view" />
 </com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
new file mode 100644
index 0000000..813787e
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.dreams.DreamOverlayStatusBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dream_overlay_status_bar"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/dream_overlay_status_bar_height"
+    android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
+    android:paddingStart="@dimen/dream_overlay_status_bar_margin"
+    app:layout_constraintTop_toTopOf="parent">
+
+    <com.android.systemui.dreams.DreamOverlayDotImageView
+        android:id="@+id/dream_overlay_notification_indicator"
+        android:layout_width="@dimen/dream_overlay_notification_indicator_size"
+        android:layout_height="@dimen/dream_overlay_notification_indicator_size"
+        android:visibility="gone"
+        android:contentDescription="@string/dream_overlay_status_bar_notification_indicator"
+        app:dotColor="@android:color/white"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <LinearLayout
+        android:id="@+id/dream_overlay_system_status"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_assistant_guest_mode_enabled"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_account_circle"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription=
+                "@string/dream_overlay_status_bar_assistant_guest_mode_enabled" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_alarm_set"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_alarm"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_alarm_set" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_priority_mode"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_remove_circle"
+            android:tint="@android:color/white"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_priority_mode" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:id="@+id/dream_overlay_wifi_status"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:src="@drawable/ic_signal_wifi_off"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
+
+        <com.android.systemui.dreams.DreamOverlayDotImageView
+            android:id="@+id/dream_overlay_camera_mic_off"
+            android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size"
+            android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size"
+            android:layout_gravity="center_vertical"
+            android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+            android:visibility="gone"
+            android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off"
+            app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" />
+
+    </LinearLayout>
+</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 2992859..c5e005c 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -199,5 +199,9 @@
     </declare-styleable>
 
     <attr name="overlayButtonTextColor" format="color" />
+
+    <declare-styleable name="DreamOverlayDotImageView">
+        <attr name="dotColor" format="color" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f4e7cf3..dc74700 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -225,4 +225,6 @@
     <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
     <color name="connected_network_primary_color">#191C18</color>
     <color name="connected_network_secondary_color">#41493D</color>
+
+    <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3704134..fcf60bf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1332,12 +1332,16 @@
     <dimen name="fgs_manager_min_width_minor">100%</dimen>
 
     <!-- Dream overlay related dimensions -->
-    <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+    <dimen name="dream_overlay_status_bar_height">60dp</dimen>
     <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
     <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
+    <dimen name="dream_overlay_status_bar_icon_size">
+        @*android:dimen/status_bar_system_icon_size</dimen>
     <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications
          shade. -->
     <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen>
+    <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
+    <dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
 
     <!-- Dream overlay complications related dimensions -->
     <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3f80647..df16b0d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2407,4 +2407,20 @@
     <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string>
     <!-- Manage users - For system user management [CHAR LIMIT=40]  -->
     <string name="manage_users">Manage users</string>
+
+    <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] -->
+    <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string>
+
+    <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string>
+    <!-- Content description for the priority mode icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_priority_mode">Priority mode</string>
+    <!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_alarm_set">Alarm set</string>
+    <!-- Content description for the assistant guest mode enabled icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_assistant_guest_mode_enabled">Assistant guest mode enabled</string>
+    <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
+    <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+    <string name="dream_overlay_status_bar_notification_indicator">There are notifications</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9d65c38..f2eaa75 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -381,12 +381,19 @@
         <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
         <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
         <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+        <item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
+        <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
     </style>
 
     <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
         <item name="android:layout">@layout/alert_dialog_systemui</item>
     </style>
 
+    <style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog">
+        <item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item>
+        <item name="android:paddingBottom">@dimen/dialog_bottom_padding</item>
+    </style>
+
     <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
 
     <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
@@ -962,6 +969,11 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
+    <style name="Widget.Dialog.Button.Large">
+        <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
+        <item name="android:minHeight">56dp</item>
+    </style>
+
     <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
         <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 08b4d3f..2b1c47f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -103,8 +103,8 @@
     // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
     // stack.
     public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
-    // The global actions dialog is showing
-    public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15;
+    // A SysUI dialog is showing.
+    public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
     // The one-handed mode is active
     public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
     // Allow system gesture no matter the system bar(s) is visible or not
@@ -140,7 +140,7 @@
             SYSUI_STATE_TRACING_ENABLED,
             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
             SYSUI_STATE_BUBBLES_EXPANDED,
-            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING,
+            SYSUI_STATE_DIALOG_SHOWING,
             SYSUI_STATE_ONE_HANDED_ACTIVE,
             SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
             SYSUI_STATE_IME_SHOWING,
@@ -166,7 +166,7 @@
         str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
                 ? "keygrd_occluded" : "");
         str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
-        str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : "");
+        str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
         str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
         str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
@@ -256,7 +256,7 @@
     public static boolean isBackGestureDisabled(int sysuiStateFlags) {
         // Always allow when the bouncer/global actions is showing (even on top of the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
-                || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) {
+                || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0) {
             return false;
         }
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b32c2b6..84b6ace 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -38,6 +38,7 @@
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -107,6 +108,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -372,6 +374,8 @@
     @Inject Lazy<AmbientState> mAmbientStateLazy;
     @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
     @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
+    @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
+    @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
 
     @Inject
     public Dependency() {
@@ -592,6 +596,8 @@
         mProviders.put(AmbientState.class, mAmbientStateLazy::get);
         mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
         mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
+        mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
+        mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
new file mode 100644
index 0000000..02a8b39a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.AlphaOptimizedImageView;
+
+/**
+ * An {@link AlphaOptimizedImageView} that is responsible for rendering a dot. Used by
+ * {@link DreamOverlayStatusBarView}.
+ */
+public class DreamOverlayDotImageView extends AlphaOptimizedImageView {
+    private final @ColorInt int mDotColor;
+
+    public DreamOverlayDotImageView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+                R.styleable.DreamOverlayDotImageView, 0, 0);
+
+        try {
+            mDotColor = a.getColor(R.styleable.DreamOverlayDotImageView_dotColor, Color.WHITE);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setImageDrawable(new DotDrawable(mDotColor));
+    }
+
+    private static class DotDrawable extends Drawable {
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private Bitmap mDotBitmap;
+        private final Rect mBounds = new Rect();
+        private final @ColorInt int mDotColor;
+
+        DotDrawable(@ColorInt int color) {
+            mDotColor = color;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas) {
+            if (mBounds.isEmpty()) {
+                return;
+            }
+
+            if (mDotBitmap == null) {
+                mDotBitmap = createBitmap(mBounds.width(), mBounds.height());
+            }
+
+            canvas.drawBitmap(mDotBitmap, null, mBounds, mPaint);
+        }
+
+        @Override
+        protected void onBoundsChange(Rect bounds) {
+            super.onBoundsChange(bounds);
+            mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+            // Make sure to regenerate the dot bitmap when the bounds change.
+            mDotBitmap = null;
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+        }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+
+        private Bitmap createBitmap(int width, int height) {
+            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            paint.setColor(mDotColor);
+            canvas.drawCircle(width / 2.f, height / 2.f, Math.min(width, height) / 2.f, paint);
+            return bitmap;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 9847ef6..2d96920 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -25,17 +25,13 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 /**
  * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
- * dream. The status bar includes status icons such as battery and wifi.
+ * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi".
  */
-public class DreamOverlayStatusBarView extends ConstraintLayout implements
-        BatteryStateChangeCallback {
+public class DreamOverlayStatusBarView extends ConstraintLayout {
 
-    private BatteryMeterView mBatteryView;
     private ImageView mWifiStatusView;
 
     public DreamOverlayStatusBarView(Context context) {
@@ -59,20 +55,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
-                "R.id.dream_overlay_battery must not be null");
         mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
                 "R.id.dream_overlay_wifi_status must not be null");
-
-        mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
-    }
-
-    /**
-     * Whether to show the battery percent text next to the battery status icons.
-     * @param show True if the battery percent text should be shown.
-     */
-    void showBatteryPercentText(boolean show) {
-        mBatteryView.setForceShowPercent(show);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 5674b9f..ed82ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -17,24 +17,19 @@
 package com.android.systemui.dreams;
 
 import android.annotation.IntDef;
-import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 
-import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.ViewController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * View controller for {@link DreamOverlayStatusBarView}.
@@ -52,21 +47,7 @@
     private static final int WIFI_STATUS_UNAVAILABLE = 1;
     private static final int WIFI_STATUS_AVAILABLE = 2;
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
-            BATTERY_STATUS_UNKNOWN,
-            BATTERY_STATUS_NOT_CHARGING,
-            BATTERY_STATUS_CHARGING
-    })
-    private @interface BatteryStatus {}
-    private static final int BATTERY_STATUS_UNKNOWN = 0;
-    private static final int BATTERY_STATUS_NOT_CHARGING = 1;
-    private static final int BATTERY_STATUS_CHARGING = 2;
-
-    private final BatteryController mBatteryController;
-    private final BatteryMeterViewController mBatteryMeterViewController;
     private final ConnectivityManager mConnectivityManager;
-    private final boolean mShowPercentAvailable;
 
     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
             .clearCapabilities()
@@ -91,43 +72,18 @@
         }
     };
 
-    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
-            new BatteryController.BatteryStateChangeCallback() {
-                @Override
-                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-                    DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
-                }
-            };
-
     private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
-    private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
 
     @Inject
     public DreamOverlayStatusBarViewController(
-            Context context,
             DreamOverlayStatusBarView view,
-            BatteryController batteryController,
-            @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
-                    BatteryMeterViewController batteryMeterViewController,
             ConnectivityManager connectivityManager) {
         super(view);
-        mBatteryController = batteryController;
-        mBatteryMeterViewController = batteryMeterViewController;
         mConnectivityManager = connectivityManager;
-
-        mShowPercentAvailable = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_battery_percentage_setting_available);
-    }
-
-    @Override
-    protected void onInit() {
-        super.onInit();
-        mBatteryMeterViewController.init();
     }
 
     @Override
     protected void onViewAttached() {
-        mBatteryController.addCallback(mBatteryStateChangeCallback);
         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
 
         NetworkCapabilities capabilities =
@@ -140,7 +96,6 @@
 
     @Override
     protected void onViewDetached() {
-        mBatteryController.removeCallback(mBatteryStateChangeCallback);
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
     }
 
@@ -155,18 +110,4 @@
             mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
         }
     }
-
-    /**
-     * The battery level has changed. Update the battery status icon as appropriate.
-     * @param charging Whether the battery is currently charging.
-     */
-    private void onBatteryLevelChanged(boolean charging) {
-        final int newBatteryStatus =
-                charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
-        if (mBatteryStatus != newBatteryStatus) {
-            mBatteryStatus = newBatteryStatus;
-            mView.showBatteryPercentText(
-                    mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4eb5cb9..839a05e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.dreams.dagger;
 
-import android.content.ContentResolver;
 import android.content.res.Resources;
-import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
@@ -28,15 +26,9 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
-import com.android.systemui.battery.BatteryMeterView;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.tuner.TunerService;
 
 import javax.inject.Named;
 
@@ -47,9 +39,6 @@
 /** Dagger module for {@link DreamOverlayComponent}. */
 @Module
 public abstract class DreamOverlayModule {
-    private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
-    public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
-            "dream_overlay_battery_controller";
     public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
     public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -86,37 +75,6 @@
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    @Named(DREAM_OVERLAY_BATTERY_VIEW)
-    static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
-        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
-                "R.id.battery must not be null");
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
-    @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
-    static BatteryMeterViewController providesBatteryMeterViewController(
-            @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
-            ConfigurationController configurationController,
-            TunerService tunerService,
-            BroadcastDispatcher broadcastDispatcher,
-            @Main Handler mainHandler,
-            ContentResolver contentResolver,
-            BatteryController batteryController) {
-        return new BatteryMeterViewController(
-                batteryMeterView,
-                configurationController,
-                tunerService,
-                broadcastDispatcher,
-                mainHandler,
-                contentResolver,
-                batteryController);
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
     @Named(MAX_BURN_IN_OFFSET)
     static int providesMaxBurnInOffset(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 84fa6a6..e3886cd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -27,7 +27,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -117,7 +116,6 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
@@ -125,7 +123,6 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -200,7 +197,6 @@
     private final TelecomManager mTelecomManager;
     private final MetricsLogger mMetricsLogger;
     private final UiEventLogger mUiEventLogger;
-    private final SysUiState mSysUiState;
 
     // Used for RingerModeTracker
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -241,7 +237,6 @@
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
     private final Optional<StatusBar> mStatusBarOptional;
-    private final SystemUIDialogManager mDialogManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -347,13 +342,11 @@
             @Background Executor backgroundExecutor,
             UiEventLogger uiEventLogger,
             RingerModeTracker ringerModeTracker,
-            SysUiState sysUiState,
             @Main Handler handler,
             PackageManager packageManager,
             Optional<StatusBar> statusBarOptional,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DialogLaunchAnimator dialogLaunchAnimator,
-            SystemUIDialogManager dialogManager) {
+            DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -379,13 +372,11 @@
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
         mRingerModeTracker = ringerModeTracker;
-        mSysUiState = sysUiState;
         mMainHandler = handler;
         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
         mStatusBarOptional = statusBarOptional;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mDialogManager = dialogManager;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -682,11 +673,10 @@
 
         ActionsDialogLite dialog = new ActionsDialogLite(mContext,
                 com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
-                mAdapter, mOverflowAdapter, mSysuiColorExtractor,
-                mStatusBarService, mNotificationShadeWindowController,
-                mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils,
-                mDialogManager);
+                mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
+                mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
+                mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor,
+                mLockPatternUtils);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2165,7 +2155,6 @@
         private boolean mKeyguardShowing;
         protected float mScrimAlpha;
         protected final NotificationShadeWindowController mNotificationShadeWindowController;
-        protected final SysUiState mSysUiState;
         private ListPopupWindow mOverflowPopup;
         private Dialog mPowerOptionsDialog;
         protected final Runnable mOnRefreshCallback;
@@ -2226,15 +2215,13 @@
                 MyOverflowAdapter overflowAdapter,
                 SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                 NotificationShadeWindowController notificationShadeWindowController,
-                SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing,
+                Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
-                Optional<StatusBar> statusBarOptional,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
-                SystemUIDialogManager systemUiDialogManager) {
+                Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor,
+                LockPatternUtils lockPatternUtils) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
             // dismiss this dialog when the device is locked.
-            super(context, themeRes, false /* dismissOnDeviceLock */,
-                    systemUiDialogManager);
+            super(context, themeRes, false /* dismissOnDeviceLock */);
             mContext = context;
             mAdapter = adapter;
             mOverflowAdapter = overflowAdapter;
@@ -2242,7 +2229,6 @@
             mColorExtractor = sysuiColorExtractor;
             mStatusBarService = statusBarService;
             mNotificationShadeWindowController = notificationShadeWindowController;
-            mSysUiState = sysuiState;
             mOnRefreshCallback = onRefreshCallback;
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
@@ -2463,8 +2449,6 @@
         public void show() {
             super.show();
             mNotificationShadeWindowController.setRequestTopUi(true, TAG);
-            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
-                    .commitUpdate(mContext.getDisplayId());
 
             // By default this dialog windowAnimationStyle is null, and therefore windowAnimations
             // should be equal to 0 which means we need to animate the dialog in-window. If it's not
@@ -2563,9 +2547,6 @@
             dismissPowerOptions();
 
             mNotificationShadeWindowController.setRequestTopUi(false, TAG);
-            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
-                    .commitUpdate(mContext.getDisplayId());
-
             super.dismiss();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 1d1a230..7b85050 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -531,7 +531,8 @@
         foregroundExecutor.execute {
             onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
-                    null, packageName, token, appIntent, device = null, active = false,
+                    MediaButton(playOrPause = mediaAction), packageName, token, appIntent,
+                    device = null, active = false,
                     resumeAction = resumeAction, resumption = true, notificationKey = packageName,
                     hasCheckedForResume = true, lastActive = lastActive))
         }
@@ -951,6 +952,7 @@
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
             val updated = removed.copy(token = null, actions = listOf(resumeAction),
+                    semanticActions = MediaButton(playOrPause = resumeAction),
                     actionsToShowInCompact = listOf(0), active = false, resumption = true,
                     isPlaying = false, isClearable = true)
             val pkg = removed.packageName
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7bb5454..04a324b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -56,7 +56,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Base dialog for media output UI
@@ -99,9 +98,8 @@
         }
     };
 
-    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController,
-            SystemUIDialogManager dialogManager) {
-        super(context, R.style.Theme_SystemUI_Dialog_Media, dialogManager);
+    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
+        super(context, R.style.Theme_SystemUI_Dialog_Media);
 
         // Save the context that is wrapped with our theme.
         mContext = getContext();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 7bc0f52..e929b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -66,7 +66,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -91,7 +90,6 @@
     private final ShadeController mShadeController;
     private final ActivityStarter mActivityStarter;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
-    private final SystemUIDialogManager mDialogManager;
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
     private final boolean mVolumeAdjustmentForRemoteGroupSessions;
@@ -119,7 +117,7 @@
             boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
             lbm, ShadeController shadeController, ActivityStarter starter,
             CommonNotifCollection notifCollection, UiEventLogger uiEventLogger,
-            DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) {
+            DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -135,7 +133,6 @@
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
-        mDialogManager = dialogManager;
         mColorActiveItem = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_active_item_main_content);
         mColorInactiveItem = Utils.getColorStateListDefaultColor(mContext,
@@ -610,10 +607,9 @@
         // We show the output group dialog from the output dialog.
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
-                mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator,
-                mDialogManager);
+                mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
         MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
-                controller, mDialogManager);
+                controller);
         mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 4e9da55..7696a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -29,7 +29,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output transferring.
@@ -39,9 +38,8 @@
     final UiEventLogger mUiEventLogger;
 
     MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController, UiEventLogger uiEventLogger,
-            SystemUIDialogManager dialogManager) {
-        super(context, mediaOutputController, dialogManager);
+            mediaOutputController, UiEventLogger uiEventLogger) {
+        super(context, mediaOutputController);
         mUiEventLogger = uiEventLogger;
         mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index e1e7fa3..9e252ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.phone.ShadeController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import javax.inject.Inject
 
 /**
@@ -39,8 +38,7 @@
     private val starter: ActivityStarter,
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
-    private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val dialogManager: SystemUIDialogManager
+    private val dialogLaunchAnimator: DialogLaunchAnimator
 ) {
     companion object {
         var mediaOutputDialog: MediaOutputDialog? = null
@@ -53,9 +51,8 @@
 
         val controller = MediaOutputController(context, packageName, aboveStatusBar,
             mediaSessionManager, lbm, shadeController, starter, notifCollection,
-            uiEventLogger, dialogLaunchAnimator, dialogManager)
-        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger,
-                dialogManager)
+            uiEventLogger, dialogLaunchAnimator)
+        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
         mediaOutputDialog = dialog
 
         // Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index 9f752b9..f1c6601 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -25,7 +25,6 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output group.
@@ -34,8 +33,8 @@
 public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
 
     MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController, SystemUIDialogManager dialogManager) {
-        super(context, mediaOutputController, dialogManager);
+            mediaOutputController) {
+        super(context, mediaOutputController);
         mMediaOutputController.resetGroupMediaDevices();
         mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 6ea7aec..80a7a4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -274,11 +274,21 @@
             };
 
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
-        // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
-        // gestural mode, the entire nav bar should be touchable.
+        // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown
+        // while in gesture nav in SUW, the entire nav bar should be touchable.
         if (!mEdgeBackGestureHandler.isHandlingGestures()) {
-            info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
-            return;
+            // We're in 2/3 button mode OR back button force-shown in SUW
+            if (!mImeVisible) {
+                // IME not showing, take all touches
+                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                return;
+            }
+  
+            if (!isImeRenderingNavButtons()) {
+                // IME showing but not drawing any buttons, take all touches
+                info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+                return;     
+            }
         }
 
         // When in gestural and the IME is showing, don't use the nearest region since it will take
@@ -775,14 +785,10 @@
 
         updateRecentsIcon();
 
-        boolean isImeRenderingNavButtons = mImeDrawsImeNavBar
-                && mImeCanRenderGesturalNavButtons
-                && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
-
         // Update IME button visibility, a11y and rotate button always overrides the appearance
         boolean disableImeSwitcher =
                 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0
-                || isImeRenderingNavButtons;
+                || isImeRenderingNavButtons();
         mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher);
 
         mBarTransitions.reapplyDarkIntensity();
@@ -799,7 +805,7 @@
 
         boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0))
-                || isImeRenderingNavButtons;
+                || isImeRenderingNavButtons();
 
         // When screen pinning, don't hide back and home when connected service or back and
         // recents buttons when disconnected from launcher service in screen pinning mode,
@@ -832,6 +838,15 @@
         notifyActiveTouchRegions();
     }
 
+    /**
+     * Returns whether the IME is currently visible and drawing the nav buttons.
+     */
+    private boolean isImeRenderingNavButtons() {
+        return mImeDrawsImeNavBar
+                && mImeCanRenderGesturalNavButtons
+                && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
+    }
+
     @VisibleForTesting
     boolean isRecentsButtonDisabled() {
         return mUseCarModeUi || !isOverviewEnabled()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index e088f54..5d6bbae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -202,11 +202,12 @@
                         mActivityStarter
                                 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
                                         controller);
-                    });
+                    }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */);
             holder.init(dialog);
             SystemUIDialog.setShowForAllUsers(dialog, true);
             SystemUIDialog.registerDismissListener(dialog);
             SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing());
+            SystemUIDialog.setDialogSize(dialog);
 
             mUiHandler.post(() -> {
                 if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 06b739b..c2c40d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -17,6 +17,11 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.widget.Toast.LENGTH_SHORT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -33,13 +38,15 @@
 import android.util.Log;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.Toast;
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -55,12 +62,15 @@
 
     private final Context mContext;
     private final HeadsUpManager mHeadsUpManager;
+    private final ShadeController mShadeController;
 
     @Inject
     public ExpandableNotificationRowDragController(Context context,
-            HeadsUpManager headsUpManager) {
+            HeadsUpManager headsUpManager,
+            ShadeController shadeController) {
         mContext = context;
         mHeadsUpManager = headsUpManager;
+        mShadeController = shadeController;
 
         init();
     }
@@ -87,6 +97,16 @@
         final PendingIntent contentIntent = notification.contentIntent != null
                 ? notification.contentIntent
                 : notification.fullScreenIntent;
+        if (contentIntent == null) {
+            if (!enr.isPinned()) {
+                // We dismiss the shade for consistency, but also because toasts currently don't
+                // show above the shade
+                dismissShade();
+            }
+            Toast.makeText(mContext, R.string.drag_split_not_supported, LENGTH_SHORT)
+                 .show();
+            return;
+        }
         Bitmap iconBitmap = getBitmapFromDrawable(
                 getPkgIcon(enr.getEntry().getSbn().getPackageName()));
 
@@ -97,15 +117,30 @@
         ClipDescription clipDescription = new ClipDescription("Drag And Drop",
                 new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY});
         Intent dragIntent = new Intent();
-        dragIntent.putExtra("android.intent.extra.PENDING_INTENT", contentIntent);
+        dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent);
         dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
         ClipData.Item item = new ClipData.Item(dragIntent);
         ClipData dragData = new ClipData(clipDescription, item);
         View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
         view.setOnDragListener(getDraggedViewDragListener());
-        view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL);
+        boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL
+                | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION);
+        if (result) {
+            view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            if (enr.isPinned()) {
+                mHeadsUpManager.releaseAllImmediately();
+            } else {
+                dismissShade();
+            }
+        }
     }
 
+    private void dismissShade() {
+        // Speed up dismissing the shade since the drag needs to be handled by
+        // the shell layer underneath
+        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+                false /* delayed */, 1.1f /* speedUpFactor */);
+    }
 
     private Drawable getPkgIcon(String pkgName) {
         Drawable pkgicon = null;
@@ -145,16 +180,6 @@
         return (view, dragEvent) -> {
             switch (dragEvent.getAction()) {
                 case DragEvent.ACTION_DRAG_STARTED:
-                    view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                    if (view instanceof ExpandableNotificationRow) {
-                        ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
-                        if (enr.isPinned()) {
-                            mHeadsUpManager.releaseAllImmediately();
-                        } else {
-                            Dependency.get(ShadeController.class).animateCollapsePanels(
-                                    CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-                        }
-                    }
                     return true;
                 case DragEvent.ACTION_DRAG_ENDED:
                     if (dragEvent.getResult()) {
@@ -162,10 +187,55 @@
                             ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
                             enr.dragAndDropSuccess();
                         }
+                    } else {
+                        // Fade out the drag surface in place instead of animating back to the
+                        // start position now that the shade is closed
+                        fadeOutAndRemoveDragSurface(dragEvent);
                     }
+                    // Clear the drag listener set above
+                    view.setOnDragListener(null);
                     return true;
             }
             return false;
         };
     }
+
+    private void fadeOutAndRemoveDragSurface(DragEvent dragEvent) {
+        SurfaceControl dragSurface = dragEvent.getDragSurface();
+        SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        ValueAnimator returnAnimator = ValueAnimator.ofFloat(0f, 1f);
+        returnAnimator.setDuration(200);
+        returnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        returnAnimator.addUpdateListener(animation -> {
+            float t = animation.getAnimatedFraction();
+            float alpha = 1f - t;
+            tx.setAlpha(dragSurface, alpha);
+            tx.apply();
+        });
+        returnAnimator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCanceled = false;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                cleanUpSurface();
+                mCanceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCanceled) {
+                    // Already handled above
+                    return;
+                }
+                cleanUpSurface();
+            }
+
+            private void cleanUpSurface() {
+                tx.remove(dragSurface);
+                tx.apply();
+                tx.close();
+            }
+        });
+        returnAnimator.start();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 6d774838..05fba54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -28,6 +28,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.assist.AssistManager;
@@ -46,8 +48,11 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
 
 import java.util.ArrayList;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -73,6 +78,8 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    @Nullable
+    private final FoldAodAnimationController mFoldAodAnimationController;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
     private final BatteryController mBatteryController;
     private final ScrimController mScrimController;
@@ -105,6 +112,7 @@
             Lazy<AssistManager> assistManagerLazy,
             DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
             PulseExpansionHandler pulseExpansionHandler,
+            Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             AuthController authController,
@@ -128,6 +136,8 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mFoldAodAnimationController = sysUIUnfoldComponent
+                .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -215,6 +225,9 @@
 
         mStatusBarStateController.setIsDozing(dozing);
         mNotificationShadeWindowViewController.setDozing(dozing);
+        if (mFoldAodAnimationController != null) {
+            mFoldAodAnimationController.setIsDozing(dozing);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9722528..79d646c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -42,7 +42,10 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.shared.system.QuickStepContract;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,7 +67,8 @@
     private final Context mContext;
     @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
-    @Nullable private final SystemUIDialogManager mDialogManager;
+    private final SystemUIDialogManager mDialogManager;
+    private final SysUiState mSysUiState;
 
     private int mLastWidth = Integer.MIN_VALUE;
     private int mLastHeight = Integer.MIN_VALUE;
@@ -77,24 +81,11 @@
         this(context, R.style.Theme_SystemUI_Dialog);
     }
 
-    public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) {
-        this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager);
-    }
-
     public SystemUIDialog(Context context, int theme) {
         this(context, theme, true /* dismissOnDeviceLock */);
     }
 
-    public SystemUIDialog(Context context, int theme, SystemUIDialogManager dialogManager) {
-        this(context, theme, true /* dismissOnDeviceLock */, dialogManager);
-    }
-
     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
-        this(context, theme, dismissOnDeviceLock, null);
-    }
-
-    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
-            @Nullable SystemUIDialogManager dialogManager) {
         super(context, theme);
         mContext = context;
 
@@ -104,7 +95,12 @@
         getWindow().setAttributes(attrs);
 
         mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
-        mDialogManager = dialogManager;
+
+        // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
+        // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
+        // the content and attach listeners.
+        mDialogManager = Dependency.get(SystemUIDialogManager.class);
+        mSysUiState = Dependency.get(SysUiState.class);
     }
 
     @Override
@@ -174,13 +170,11 @@
             mDismissReceiver.register();
         }
 
-        if (mDialogManager != null) {
-            mDialogManager.setShowing(this, true);
-        }
-
         // Listen for configuration changes to resize this dialog window. This is mostly necessary
         // for foldables that often go from large <=> small screen when folding/unfolding.
         ViewRootImpl.addConfigCallback(this);
+        mDialogManager.setShowing(this, true);
+        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true);
     }
 
     @Override
@@ -191,11 +185,9 @@
             mDismissReceiver.unregister();
         }
 
-        if (mDialogManager != null) {
-            mDialogManager.setShowing(this, false);
-        }
-
         ViewRootImpl.removeConfigCallback(this);
+        mDialogManager.setShowing(this, false);
+        mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false);
     }
 
     public void setShowForAllUsers(boolean show) {
@@ -401,10 +393,13 @@
         private final Dialog mDialog;
         private boolean mRegistered;
         private final BroadcastDispatcher mBroadcastDispatcher;
+        private final DialogLaunchAnimator mDialogLaunchAnimator;
 
         DismissReceiver(Dialog dialog) {
             mDialog = dialog;
+            // TODO(b/219008720): Remove those calls to Dependency.get.
             mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+            mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class);
         }
 
         void register() {
@@ -421,6 +416,10 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
+            // These broadcast are usually received when locking the device, swiping up to home
+            // (which collapses the shade), etc. In those cases, we usually don't want to animate
+            // back into the view.
+            mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
             mDialog.dismiss();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 2a9076e..e2374ad 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -16,9 +16,12 @@
 
 package com.android.systemui.unfold
 
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
 import android.os.Handler
 import android.os.PowerManager
 import android.provider.Settings
+import androidx.core.view.OneShotPreDrawListener
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
@@ -27,6 +30,8 @@
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
 import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.Executor
+import java.util.function.Consumer
 import javax.inject.Inject
 
 /**
@@ -38,13 +43,21 @@
 @Inject
 constructor(
     @Main private val handler: Handler,
+    @Main private val executor: Executor,
+    private val context: Context,
+    private val deviceStateManager: DeviceStateManager,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val globalSettings: GlobalSettings
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
-    private var alwaysOnEnabled: Boolean = false
-    private var isScrimOpaque: Boolean = false
     private lateinit var statusBar: StatusBar
+
+    private var isFolded = false
+    private var isFoldHandled = true
+
+    private var alwaysOnEnabled: Boolean = false
+    private var isDozing: Boolean = false
+    private var isScrimOpaque: Boolean = false
     private var pendingScrimReadyCallback: Runnable? = null
 
     private var shouldPlayAnimation = false
@@ -62,6 +75,7 @@
     override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
         this.statusBar = statusBar
 
+        deviceStateManager.registerCallback(executor, FoldListener())
         wakefulnessLifecycle.addObserver(this)
     }
 
@@ -84,7 +98,7 @@
     override fun onStartedWakingUp() {
         if (isAnimationPlaying) {
             handler.removeCallbacks(startAnimationRunnable)
-            statusBar.notificationPanelViewController.cancelFoldToAodAnimation();
+            statusBar.notificationPanelViewController.cancelFoldToAodAnimation()
         }
 
         setAnimationState(playing = false)
@@ -105,11 +119,24 @@
      */
     fun onScreenTurningOn(onReady: Runnable) {
         if (shouldPlayAnimation) {
+            // The device was not dozing and going to sleep after folding, play the animation
+
             if (isScrimOpaque) {
                 onReady.run()
             } else {
                 pendingScrimReadyCallback = onReady
             }
+        } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) {
+            // Screen turning on for the first time after folding and we are already dozing
+            // We should play the folding to AOD animation
+
+            setAnimationState(playing = true)
+            statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
+
+            // We don't need to wait for the scrim as it is already displayed
+            // but we should wait for the initial animation preparations to be drawn
+            // (setting initial alpha/translation)
+            OneShotPreDrawListener.add(statusBar.notificationPanelViewController.view, onReady)
         } else {
             // No animation, call ready callback immediately
             onReady.run()
@@ -136,6 +163,10 @@
         }
     }
 
+    fun setIsDozing(dozing: Boolean) {
+        isDozing = dozing
+    }
+
     override fun isAnimationPlaying(): Boolean = isAnimationPlaying
 
     override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying()
@@ -166,4 +197,15 @@
     interface FoldAodAnimationStatus {
         fun onFoldToAodAnimationChanged()
     }
+
+    private inner class FoldListener :
+        DeviceStateManager.FoldStateListener(
+            context,
+            Consumer { isFolded ->
+                if (!isFolded) {
+                    // We are unfolded now, reset the fold handle status
+                    isFoldHandled = false
+                }
+                this.isFolded = isFolded
+            })
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6fefce2..b2a79b0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -21,7 +21,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -97,7 +97,7 @@
         implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
     private static final String TAG = WMShell.class.getName();
     private static final int INVALID_SYSUI_STATE_MASK =
-            SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+            SYSUI_STATE_DIALOG_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
                     | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
                     | SYSUI_STATE_BOUNCER_SHOWING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 40632a8..7a0db1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -42,6 +42,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -112,6 +113,11 @@
         // KeyguardUpdateMonitor to be created (injected).
         // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
         mDependency.injectMockDependency(SmartReplyController.class);
+
+        // Make sure that all tests on any SystemUIDialog does not crash because this dependency
+        // is missing (constructing the actual one would throw).
+        // TODO(b/219008720): Remove this.
+        mDependency.injectMockDependency(SystemUIDialogManager.class);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 7f72dda..6587029 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -29,8 +29,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.statusbar.policy.BatteryController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,10 +44,6 @@
     @Mock
     DreamOverlayStatusBarView mView;
     @Mock
-    BatteryController mBatteryController;
-    @Mock
-    BatteryMeterViewController mBatteryMeterViewController;
-    @Mock
     ConnectivityManager mConnectivityManager;
     @Mock
     NetworkCapabilities mNetworkCapabilities;
@@ -61,22 +55,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mController = new DreamOverlayStatusBarViewController(
-                mContext, mView, mBatteryController, mBatteryMeterViewController,
-                mConnectivityManager);
-    }
-
-    @Test
-    public void testOnInitInitializesControllers() {
-        mController.onInit();
-        verify(mBatteryMeterViewController).init();
-    }
-
-    @Test
-    public void testOnViewAttachedAddsBatteryControllerCallback() {
-        mController.onViewAttached();
-        verify(mBatteryController)
-                .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+        mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager);
     }
 
     @Test
@@ -113,13 +92,6 @@
     }
 
     @Test
-    public void testOnViewDetachedRemovesBatteryControllerCallback() {
-        mController.onViewDetached();
-        verify(mBatteryController)
-                .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
-    }
-
-    @Test
     public void testOnViewDetachedUnregistersNetworkCallback() {
         mController.onViewDetached();
         verify(mConnectivityManager)
@@ -127,26 +99,6 @@
     }
 
     @Test
-    public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
-        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
-                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
-        mController.onViewAttached();
-        verify(mBatteryController).addCallback(callbackCapture.capture());
-        callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
-        verify(mView).showBatteryPercentText(true);
-    }
-
-    @Test
-    public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
-        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
-                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
-        mController.onViewAttached();
-        verify(mBatteryController).addCallback(callbackCapture.capture());
-        callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
-        verify(mView).showBatteryPercentText(false);
-    }
-
-    @Test
     public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
         // Make sure wifi starts out unavailable when onViewAttached is called.
         when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 71fc8ee..953be7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -57,13 +56,11 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -112,7 +109,6 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private RingerModeTracker mRingerModeTracker;
     @Mock private RingerModeLiveData mRingerModeLiveData;
-    @Mock private SysUiState mSysUiState;
     @Mock private PackageManager mPackageManager;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
@@ -120,7 +116,6 @@
     @Mock private StatusBar mStatusBar;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
-    @Mock private SystemUIDialogManager mDialogManager;
 
     private TestableLooper mTestableLooper;
 
@@ -161,19 +156,16 @@
                 mBackgroundExecutor,
                 mUiEventLogger,
                 mRingerModeTracker,
-                mSysUiState,
                 mHandler,
                 mPackageManager,
                 Optional.of(mStatusBar),
                 mKeyguardUpdateMonitor,
-                mDialogLaunchAnimator,
-                mDialogManager);
+                mDialogLaunchAnimator);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
         ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
         backdropColors.setMainColor(Color.BLACK);
         when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
-        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
     }
 
     @Test
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 f4fa921..021f70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -370,6 +370,7 @@
         assertThat(data.song).isEqualTo(SESSION_TITLE)
         assertThat(data.app).isEqualTo(APP_NAME)
         assertThat(data.actions).hasSize(1)
+        assertThat(data.semanticActions!!.playOrPause).isNotNull()
         assertThat(data.lastActive).isAtLeast(currentTime)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index c5c4d79..2be30b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -45,7 +45,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,7 +67,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
     private MediaOutputController mMediaOutputController;
@@ -82,7 +80,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -175,7 +173,7 @@
     class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
 
         MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
-            super(context, mediaOutputController, mDialogManager);
+            super(context, mediaOutputController);
 
             mAdapter = mMediaOutputBaseAdapter;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index bdc3117..789822e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -55,7 +55,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -94,7 +93,6 @@
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private Context mSpyContext;
     private MediaOutputController mMediaOutputController;
@@ -117,7 +115,7 @@
 
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -161,7 +159,7 @@
     public void start_withoutPackageName_verifyMediaControllerInit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         mMediaOutputController.start(mCb);
 
@@ -182,7 +180,7 @@
     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         mMediaOutputController.start(mCb);
 
@@ -453,7 +451,7 @@
     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotifCollection, mUiEventLogger, mDialogLaunchAnimator);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index ada8d35..8a3ea56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -40,7 +40,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -68,7 +67,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputDialog mMediaOutputDialog;
     private MediaOutputController mMediaOutputController;
@@ -78,10 +76,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger, mDialogManager);
+                mMediaOutputController, mUiEventLogger);
         mMediaOutputDialog.show();
 
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -127,7 +125,7 @@
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
         MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger, mDialogManager);
+                mMediaOutputController, mUiEventLogger);
         testDialog.show();
 
         testDialog.dismissDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index b114452..e8cd6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -38,7 +38,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -67,7 +66,6 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
-    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputGroupDialog mMediaOutputGroupDialog;
     private MediaOutputController mMediaOutputController;
@@ -77,10 +75,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
-                mMediaOutputController, mDialogManager);
+                mMediaOutputController);
         mMediaOutputGroupDialog.show();
         when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 24a0ad3..bc54bf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.DragEvent.ACTION_DRAG_STARTED;
 
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -36,7 +38,12 @@
 import org.junit.runner.RunWith;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -58,6 +65,7 @@
     private NotificationMenuRow mMenuRow = mock(NotificationMenuRow.class);
     private NotificationMenuRowPlugin.MenuItem mMenuItem =
             mock(NotificationMenuRowPlugin.MenuItem.class);
+    private ShadeController mShadeController = mock(ShadeController.class);
 
     @Before
     public void setUp() throws Exception {
@@ -69,11 +77,15 @@
                 mContext,
                 mDependency,
                 TestableLooper.get(this));
-        mRow = mNotificationTestHelper.createRow();
+        mRow = spy(mNotificationTestHelper.createRow());
+        Notification notification = mRow.getEntry().getSbn().getNotification();
+        notification.contentIntent = mock(PendingIntent.class);
+        doReturn(true).when(mRow).startDragAndDrop(any(), any(), any(), anyInt());
         mGroupRow = mNotificationTestHelper.createGroup(4);
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
 
-        mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager);
+        mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager,
+                mShadeController);
     }
 
     @Test
@@ -86,10 +98,6 @@
         mRow.doLongClickCallback(0, 0);
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
-
-        // Simulate the drag start
-        mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
-                null, null, false));
         verify(mHeadsUpManager, times(1)).releaseAllImmediately();
     }
 
@@ -98,14 +106,27 @@
         ExpandableNotificationRowDragController controller = createSpyController();
         mRow.setDragController(controller);
 
-        mDependency.get(ShadeController.class).instantExpandNotificationsPanel();
+        mRow.doDragCallback(0, 0);
+        verify(controller).startDragAndDrop(mRow);
+        verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
+                eq(false), anyFloat());
+    }
+
+    @Test
+    public void testDoStartDrag_noLaunchIntent() throws Exception {
+        ExpandableNotificationRowDragController controller = createSpyController();
+        mRow.setDragController(controller);
+
+        // Clear the intents
+        Notification notification = mRow.getEntry().getSbn().getNotification();
+        notification.contentIntent = null;
+        notification.fullScreenIntent = null;
+
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
 
-        // Simulate the drag start
-        mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
-                null, null, false));
-        verify(mDependency.get(ShadeController.class)).animateCollapsePanels(0, true);
+        // Verify that we never start the actual drag since there is no content
+        verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt());
     }
 
     private ExpandableNotificationRowDragController createSpyController() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 38d7ce7..6ce3b4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -59,6 +59,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -97,7 +98,7 @@
                 mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
                 mBatteryController, mScrimController, () -> mBiometricUnlockController,
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
-                mKeyguardUpdateMonitor, mPulseExpansionHandler,
+                mKeyguardUpdateMonitor, mPulseExpansionHandler, Optional.empty(),
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
                 mAuthController, mNotificationIconAreaController);
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 62da981..0e99265 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1363,8 +1363,18 @@
      * </p>
      *
      * @param displayId The logical display id
-     * @param region the new magnified region, may be empty if
-     *               magnification is not enabled (e.g. scale is 1)
+     * @param region The magnification region.
+     *               If the config mode is
+     *               {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
+     *               it is the region of the screen currently active for magnification.
+     *               the returned region will be empty if the magnification is not active
+     *               (e.g. scale is 1. And the magnification is active if magnification
+     *               gestures are enabled or if a service is running that can control
+     *               magnification.
+     *               If the config mode is
+     *               {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
+     *               it is the region of screen projected on the magnification window.
+     *               The region will be empty if magnification is not activated.
      * @param config The magnification config. That has magnification mode, the new scale and the
      *              new screen-relative center position
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index fe97a46..a958209 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -50,7 +50,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
-import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -374,9 +373,8 @@
                     .setScale(getScale())
                     .setCenterX(getCenterX())
                     .setCenterY(getCenterY()).build();
-            mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId,
-                    mMagnificationRegion,
-                    config);
+            mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId,
+                    mMagnificationRegion, config);
             if (mUnregisterPending && !isMagnifying()) {
                 unregister(mDeleteAfterUnregister);
             }
@@ -665,10 +663,10 @@
      * FullScreenMagnificationController Constructor
      */
     public FullScreenMagnificationController(@NonNull Context context,
-            @NonNull AccessibilityManagerService ams, @NonNull Object lock,
+            @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
             @NonNull MagnificationScaleProvider scaleProvider) {
-        this(new ControllerContext(context, ams,
+        this(new ControllerContext(context, traceManager,
                 LocalServices.getService(WindowManagerInternal.class),
                 new Handler(context.getMainLooper()),
                 context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
@@ -1521,7 +1519,6 @@
     @VisibleForTesting
     public static class ControllerContext {
         private final Context mContext;
-        private final AccessibilityManagerService mAms;
         private final AccessibilityTraceManager mTrace;
         private final WindowManagerInternal mWindowManager;
         private final Handler mHandler;
@@ -1531,13 +1528,12 @@
          * Constructor for ControllerContext.
          */
         public ControllerContext(@NonNull Context context,
-                @NonNull AccessibilityManagerService ams,
+                @NonNull AccessibilityTraceManager traceManager,
                 @NonNull WindowManagerInternal windowManager,
                 @NonNull Handler handler,
                 long animationDuration) {
             mContext = context;
-            mAms = ams;
-            mTrace = ams.getTraceManager();
+            mTrace = traceManager;
             mWindowManager = windowManager;
             mHandler = handler;
             mAnimationDuration = animationDuration;
@@ -1552,14 +1548,6 @@
         }
 
         /**
-         * @return AccessibilityManagerService
-         */
-        @NonNull
-        public AccessibilityManagerService getAms() {
-            return mAms;
-        }
-
-        /**
          * @return AccessibilityTraceManager
          */
         @NonNull
@@ -1632,5 +1620,17 @@
          *                           hidden.
          */
         void onImeWindowVisibilityChanged(boolean shown);
+
+        /**
+         * Called when the magnification spec changed.
+         *
+         * @param displayId The logical display id
+         * @param region    The region of the screen currently active for magnification.
+         *                  The returned region will be empty if the magnification is not active.
+         * @param config    The magnification config. That has magnification mode, the new scale and
+         *                  the new screen-relative center position
+         */
+        void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+                @NonNull MagnificationConfig config);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index c376bf8..09e82c7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -405,6 +405,12 @@
         mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
     }
 
+    @Override
+    public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
+            @NonNull MagnificationConfig config) {
+        mAms.notifyMagnificationChanged(displayId, region, config);
+    }
+
     private void disableFullScreenMagnificationIfNeeded(int displayId) {
         final FullScreenMagnificationController fullScreenMagnificationController =
                 getFullScreenMagnificationController();
@@ -590,7 +596,7 @@
         synchronized (mLock) {
             if (mFullScreenMagnificationController == null) {
                 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
-                        mAms, mLock, this, mScaleProvider);
+                        mAms.getTraceManager(), mLock, this, mScaleProvider);
             }
         }
         return mFullScreenMagnificationController;
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 8963337..7e3ede1 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -22,20 +22,18 @@
 import android.app.backup.RestoreSet;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
-import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.infra.AndroidFuture;
 
 import java.util.ArrayDeque;
-import java.util.Deque;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -49,17 +47,19 @@
 
     private final IBackupTransport mTransportBinder;
     private final TransportStatusCallbackPool mCallbackPool;
+    private final TransportFutures mTransportFutures;
 
     BackupTransportClient(IBackupTransport transportBinder) {
         mTransportBinder = transportBinder;
         mCallbackPool = new TransportStatusCallbackPool();
+        mTransportFutures = new TransportFutures();
     }
 
     /**
      * See {@link IBackupTransport#name()}.
      */
     public String name() throws RemoteException {
-        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.name(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -68,7 +68,7 @@
      * See {@link IBackupTransport#configurationIntent()}
      */
     public Intent configurationIntent() throws RemoteException {
-        AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.configurationIntent(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -77,7 +77,7 @@
      * See {@link IBackupTransport#currentDestinationString()}
      */
     public String currentDestinationString() throws RemoteException {
-        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.currentDestinationString(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -86,7 +86,7 @@
      * See {@link IBackupTransport#dataManagementIntent()}
      */
     public Intent dataManagementIntent() throws RemoteException {
-        AndroidFuture<Intent> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.dataManagementIntent(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -96,7 +96,7 @@
      */
     @Nullable
     public CharSequence dataManagementIntentLabel() throws RemoteException {
-        AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>();
+        AndroidFuture<CharSequence> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.dataManagementIntentLabel(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -105,7 +105,7 @@
      * See {@link IBackupTransport#transportDirName()}
      */
     public String transportDirName() throws RemoteException {
-        AndroidFuture<String> resultFuture = new AndroidFuture<>();
+        AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.transportDirName(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -153,7 +153,7 @@
      * See {@link IBackupTransport#requestBackupTime()}
      */
     public long requestBackupTime() throws RemoteException {
-        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.requestBackupTime(resultFuture);
         Long result = getFutureResult(resultFuture);
         return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -177,7 +177,7 @@
      * See {@link IBackupTransport#getAvailableRestoreSets()}
      */
     public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
-        AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>();
+        AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.getAvailableRestoreSets(resultFuture);
         List<RestoreSet> result = getFutureResult(resultFuture);
         return result == null ? null : result.toArray(new RestoreSet[] {});
@@ -187,7 +187,7 @@
      * See {@link IBackupTransport#getCurrentRestoreSet()}
      */
     public long getCurrentRestoreSet() throws RemoteException {
-        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.getCurrentRestoreSet(resultFuture);
         Long result = getFutureResult(resultFuture);
         return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -210,7 +210,7 @@
      * See {@link IBackupTransport#nextRestorePackage()}
      */
     public RestoreDescription nextRestorePackage() throws RemoteException {
-        AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>();
+        AndroidFuture<RestoreDescription> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.nextRestorePackage(resultFuture);
         return getFutureResult(resultFuture);
     }
@@ -245,7 +245,7 @@
      * See {@link IBackupTransport#requestFullBackupTime()}
      */
     public long requestFullBackupTime() throws RemoteException {
-        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.requestFullBackupTime(resultFuture);
         Long result = getFutureResult(resultFuture);
         return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -309,7 +309,7 @@
      */
     public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
             throws RemoteException {
-        AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Boolean> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture);
         Boolean result = getFutureResult(resultFuture);
         return result != null && result;
@@ -319,7 +319,7 @@
      * See {@link IBackupTransport#getBackupQuota(String, boolean)}
      */
     public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
-        AndroidFuture<Long> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture);
         Long result = getFutureResult(resultFuture);
         return result == null ? BackupTransport.TRANSPORT_ERROR : result;
@@ -355,18 +355,58 @@
      * See {@link IBackupTransport#getTransportFlags()}
      */
     public int getTransportFlags() throws RemoteException {
-        AndroidFuture<Integer> resultFuture = new AndroidFuture<>();
+        AndroidFuture<Integer> resultFuture = mTransportFutures.newFuture();
         mTransportBinder.getTransportFlags(resultFuture);
         Integer result = getFutureResult(resultFuture);
         return result == null ? BackupTransport.TRANSPORT_ERROR : result;
     }
 
+    /**
+     * Allows the {@link TransportConnection} to notify this client
+     * if the underlying transport has become unusable.  If that happens
+     * we want to cancel all active futures or callbacks.
+     */
+    void onBecomingUnusable() {
+        mCallbackPool.cancelActiveCallbacks();
+        mTransportFutures.cancelActiveFutures();
+    }
+
     private <T> T getFutureResult(AndroidFuture<T> future) {
         try {
             return future.get(600, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
             Slog.w(TAG, "Failed to get result from transport:", e);
             return null;
+        } finally {
+            mTransportFutures.remove(future);
+        }
+    }
+
+    private static class TransportFutures {
+        private final Object mActiveFuturesLock = new Object();
+        private final Set<AndroidFuture<?>> mActiveFutures = new HashSet<>();
+
+        <T> AndroidFuture<T> newFuture() {
+            AndroidFuture<T> future = new AndroidFuture<>();
+            synchronized (mActiveFuturesLock) {
+                mActiveFutures.add(future);
+            }
+            return future;
+        }
+
+        <T> void remove(AndroidFuture<T> future) {
+            synchronized (mActiveFuturesLock) {
+                mActiveFutures.remove(future);
+            }
+        }
+
+        void cancelActiveFutures() {
+            synchronized (mActiveFuturesLock) {
+                for (AndroidFuture<?> future : mActiveFutures) {
+                    future.cancel(true);
+                }
+                mActiveFutures.clear();
+            }
         }
     }
 
@@ -375,27 +415,47 @@
 
         private final Object mPoolLock = new Object();
         private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>();
+        private final Set<TransportStatusCallback> mActiveCallbacks = new HashSet<>();
 
         TransportStatusCallback acquire() {
             synchronized (mPoolLock) {
-                if (mCallbackPool.isEmpty()) {
-                    return new TransportStatusCallback();
-                } else {
-                    return mCallbackPool.poll();
+                TransportStatusCallback callback = mCallbackPool.poll();
+                if (callback == null) {
+                    callback = new TransportStatusCallback();
                 }
+                callback.reset();
+                mActiveCallbacks.add(callback);
+                return callback;
             }
         }
 
         void recycle(TransportStatusCallback callback) {
             synchronized (mPoolLock) {
+                mActiveCallbacks.remove(callback);
                 if (mCallbackPool.size() > MAX_POOL_SIZE) {
                     Slog.d(TAG, "TransportStatusCallback pool size exceeded");
                     return;
                 }
-
-                callback.reset();
                 mCallbackPool.add(callback);
             }
         }
+
+        void cancelActiveCallbacks() {
+            synchronized (mPoolLock) {
+                for (TransportStatusCallback callback : mActiveCallbacks) {
+                    try {
+                        callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR);
+                        // This waits for status to propagate before the callback is reset.
+                        callback.getOperationStatus();
+                    } catch (RemoteException ex) {
+                        // Nothing we can do.
+                    }
+                    if (mCallbackPool.size() < MAX_POOL_SIZE) {
+                        mCallbackPool.add(callback);
+                    }
+                }
+                mActiveCallbacks.clear();
+            }
+        }
     }
 }
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index f9a3c36..1009787 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
@@ -449,6 +449,9 @@
     private void onServiceDisconnected() {
         synchronized (mStateLock) {
             log(Priority.ERROR, "Service disconnected: client UNUSABLE");
+            if (mTransport != null) {
+                mTransport.onBecomingUnusable();
+            }
             setStateLocked(State.UNUSABLE, null);
             try {
                 // After unbindService() no calls back to mConnection
@@ -473,6 +476,9 @@
             checkStateIntegrityLocked();
 
             log(Priority.ERROR, "Binding died: client UNUSABLE");
+            if (mTransport != null) {
+                mTransport.onBecomingUnusable();
+            }
             // After unbindService() no calls back to mConnection
             switch (mState) {
                 case State.UNUSABLE:
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
index a55178c..bc5cb02 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java
@@ -75,13 +75,11 @@
             }
 
             Slog.w(TAG, "Couldn't get operation status from transport");
-            return BackupTransport.TRANSPORT_ERROR;
         } catch (InterruptedException e) {
             Slog.w(TAG, "Couldn't get operation status from transport: ", e);
-            return BackupTransport.TRANSPORT_ERROR;
-        } finally {
-            reset();
         }
+
+        return BackupTransport.TRANSPORT_ERROR;
     }
 
     synchronized void reset() {
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 21a677b8..cb28254 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -171,12 +171,20 @@
         broadcastChange(CHANGE_TYPE_REMOVED, association);
     }
 
+    /**
+     * @return a "snapshot" of the current state of the existing associations.
+     */
     public @NonNull Collection<AssociationInfo> getAssociations() {
-        final Collection<AssociationInfo> allAssociations;
         synchronized (mLock) {
-            allAssociations = mIdMap.values();
+            // IMPORTANT: make and return a COPY of the mIdMap.values(), NOT a "direct" reference.
+            // The HashMap.values() returns a collection which is backed by the HashMap, so changes
+            // to the HashMap are reflected in this collection.
+            // For us this means that if mIdMap is modified while the iteration over mIdMap.values()
+            // is in progress it may lead to "undefined results" (according to the HashMap's
+            // documentation) or cause ConcurrentModificationExceptions in the iterator (according
+            // to the bugreports...).
+            return List.copyOf(mIdMap.values());
         }
-        return Collections.unmodifiableCollection(allAssociations);
     }
 
     public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 09ef03c..111bd34 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -67,7 +67,7 @@
  *
  * @hide Only for use within the system server.
  */
-public abstract class PackageManagerInternal implements PackageSettingsSnapshotProvider {
+public abstract class PackageManagerInternal {
     @IntDef(prefix = "PACKAGE_", value = {
             PACKAGE_SYSTEM,
             PACKAGE_SETUP_WIZARD,
diff --git a/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java b/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java
deleted file mode 100644
index 221f172..0000000
--- a/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.FunctionalUtils;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.PackageSetting;
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/** @hide */
-public interface PackageSettingsSnapshotProvider {
-
-    /**
-     * Run a function block that requires access to {@link PackageStateInternal} data. This will
-     * ensure the {@link PackageManagerService} lock is taken before any caller's internal lock
-     * to avoid deadlock. Note that this method may or may not lock. If a snapshot is available
-     * and valid, it will iterate the snapshot set of data.
-     */
-    void withPackageSettingsSnapshot(
-            @NonNull Consumer<Function<String, PackageStateInternal>> block);
-
-    /**
-     * Variant which returns a value to the caller.
-     * @see #withPackageSettingsSnapshot(Consumer)
-     */
-    <Output> Output withPackageSettingsSnapshotReturning(
-            @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>,
-                    Output> block);
-
-    /**
-     * Variant which throws.
-     * @see #withPackageSettingsSnapshot(Consumer)
-     */
-    <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
-            @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageStateInternal>,
-                    ExceptionType> block) throws ExceptionType;
-
-    /**
-     * Variant which throws 2 exceptions.
-     * @see #withPackageSettingsSnapshot(Consumer)
-     */
-    <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
-            withPackageSettingsSnapshotThrowing2(
-                    @NonNull FunctionalUtils.ThrowingChecked2Consumer<
-                            Function<String, PackageStateInternal>,
-                            ExceptionOne, ExceptionTwo> block)
-            throws ExceptionOne, ExceptionTwo;
-
-    /**
-     * Variant which returns a value to the caller and throws.
-     * @see #withPackageSettingsSnapshot(Consumer)
-     */
-    <Output, ExceptionType extends Exception> Output
-            withPackageSettingsSnapshotReturningThrowing(
-                    @NonNull FunctionalUtils.ThrowingCheckedFunction<
-                            Function<String, PackageStateInternal>, Output, ExceptionType> block)
-            throws ExceptionType;
-}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 9ba9d78..b813bc4 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -159,7 +159,7 @@
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
             mGameServiceController = new GameServiceController(
-                    BackgroundThread.getExecutor(),
+                    context, BackgroundThread.getExecutor(),
                     new GameServiceProviderSelectorImpl(
                             context.getResources(),
                             context.getPackageManager()),
@@ -376,9 +376,10 @@
 
     /**
      * Called by games to communicate the current state to the platform.
+     *
      * @param packageName The client package name.
-     * @param gameState An object set to the current state.
-     * @param userId The user associated with this state.
+     * @param gameState   An object set to the current state.
+     * @param userId      The user associated with this state.
      */
     public void setGameState(String packageName, @NonNull GameState gameState,
             @UserIdInt int userId) {
@@ -1373,7 +1374,7 @@
      * @hide
      */
     @VisibleForTesting
-    void updateConfigsForUser(@UserIdInt int userId, String ...packageNames) {
+    void updateConfigsForUser(@UserIdInt int userId, String... packageNames) {
         try {
             synchronized (mDeviceConfigLock) {
                 for (final String packageName : packageNames) {
@@ -1442,7 +1443,7 @@
         final List<PackageInfo> packages =
                 mPackageManager.getInstalledPackagesAsUser(0, userId);
         return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
-                == ApplicationInfo.CATEGORY_GAME)
+                        == ApplicationInfo.CATEGORY_GAME)
                 .map(e -> e.packageName)
                 .toArray(String[]::new);
     }
diff --git a/services/core/java/com/android/server/app/GameServiceConfiguration.java b/services/core/java/com/android/server/app/GameServiceConfiguration.java
new file mode 100644
index 0000000..1f31a87
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceConfiguration.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Representation of a {@link android.service.games.GameService} provider configuration.
+ */
+final class GameServiceConfiguration {
+    private final String mPackageName;
+    @Nullable
+    private final GameServiceComponentConfiguration mGameServiceComponentConfiguration;
+
+    GameServiceConfiguration(
+            @NonNull String packageName,
+            @Nullable GameServiceComponentConfiguration gameServiceComponentConfiguration) {
+        Objects.requireNonNull(packageName);
+
+        mPackageName = packageName;
+        mGameServiceComponentConfiguration = gameServiceComponentConfiguration;
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Nullable
+    public GameServiceComponentConfiguration getGameServiceComponentConfiguration() {
+        return mGameServiceComponentConfiguration;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof GameServiceConfiguration)) {
+            return false;
+        }
+
+        GameServiceConfiguration that = (GameServiceConfiguration) o;
+        return TextUtils.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mGameServiceComponentConfiguration,
+                that.mGameServiceComponentConfiguration);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackageName, mGameServiceComponentConfiguration);
+    }
+
+    @Override
+    public String toString() {
+        return "GameServiceConfiguration{"
+                + "packageName="
+                + mPackageName
+                + ", gameServiceComponentConfiguration="
+                + mGameServiceComponentConfiguration
+                + '}';
+    }
+
+    static final class GameServiceComponentConfiguration {
+        private final UserHandle mUserHandle;
+        private final ComponentName mGameServiceComponentName;
+        private final ComponentName mGameSessionServiceComponentName;
+
+        GameServiceComponentConfiguration(
+                @NonNull UserHandle userHandle, @NonNull ComponentName gameServiceComponentName,
+                @NonNull ComponentName gameSessionServiceComponentName) {
+            Objects.requireNonNull(userHandle);
+            Objects.requireNonNull(gameServiceComponentName);
+            Objects.requireNonNull(gameSessionServiceComponentName);
+
+            mUserHandle = userHandle;
+            mGameServiceComponentName = gameServiceComponentName;
+            mGameSessionServiceComponentName = gameSessionServiceComponentName;
+        }
+
+        @NonNull
+        public UserHandle getUserHandle() {
+            return mUserHandle;
+        }
+
+        @NonNull
+        public ComponentName getGameServiceComponentName() {
+            return mGameServiceComponentName;
+        }
+
+        @NonNull
+        public ComponentName getGameSessionServiceComponentName() {
+            return mGameSessionServiceComponentName;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof GameServiceComponentConfiguration)) {
+                return false;
+            }
+
+            GameServiceComponentConfiguration that =
+                    (GameServiceComponentConfiguration) o;
+            return mUserHandle.equals(that.mUserHandle) && mGameServiceComponentName.equals(
+                    that.mGameServiceComponentName)
+                    && mGameSessionServiceComponentName.equals(
+                    that.mGameSessionServiceComponentName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUserHandle,
+                    mGameServiceComponentName,
+                    mGameSessionServiceComponentName);
+        }
+
+        @Override
+        public String toString() {
+            return "GameServiceComponentConfiguration{"
+                    + "userHandle="
+                    + mUserHandle
+                    + ", gameServiceComponentName="
+                    + mGameServiceComponentName
+                    + ", gameSessionServiceComponentName="
+                    + mGameSessionServiceComponentName
+                    + "}";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java
index 397439a..db1ca97 100644
--- a/services/core/java/com/android/server/app/GameServiceController.java
+++ b/services/core/java/com/android/server/app/GameServiceController.java
@@ -19,10 +19,17 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -36,8 +43,8 @@
 final class GameServiceController {
     private static final String TAG = "GameServiceController";
 
-
     private final Object mLock = new Object();
+    private final Context mContext;
     private final Executor mBackgroundExecutor;
     private final GameServiceProviderSelector mGameServiceProviderSelector;
     private final GameServiceProviderInstanceFactory mGameServiceProviderInstanceFactory;
@@ -46,18 +53,24 @@
     @Nullable
     private volatile String mGameServiceProviderOverride;
     @Nullable
+    private BroadcastReceiver mGameServicePackageChangedReceiver;
+    @Nullable
     private volatile SystemService.TargetUser mCurrentForegroundUser;
     @GuardedBy("mLock")
     @Nullable
-    private volatile GameServiceProviderConfiguration mActiveGameServiceProviderConfiguration;
+    private volatile GameServiceComponentConfiguration mActiveGameServiceComponentConfiguration;
     @GuardedBy("mLock")
     @Nullable
     private volatile GameServiceProviderInstance mGameServiceProviderInstance;
+    @GuardedBy("mLock")
+    @Nullable
+    private volatile String mActiveGameServiceProviderPackage;
 
     GameServiceController(
-            @NonNull Executor backgroundExecutor,
+            @NonNull Context context, @NonNull Executor backgroundExecutor,
             @NonNull GameServiceProviderSelector gameServiceProviderSelector,
             @NonNull GameServiceProviderInstanceFactory gameServiceProviderInstanceFactory) {
+        mContext = context;
         mGameServiceProviderInstanceFactory = gameServiceProviderInstanceFactory;
         mBackgroundExecutor = backgroundExecutor;
         mGameServiceProviderSelector = gameServiceProviderSelector;
@@ -139,35 +152,92 @@
         }
 
         synchronized (mLock) {
-            GameServiceProviderConfiguration selectedGameServiceProviderConfiguration =
+            final GameServiceConfiguration selectedGameServiceConfiguration =
                     mGameServiceProviderSelector.get(mCurrentForegroundUser,
                             mGameServiceProviderOverride);
+            final String gameServicePackage =
+                    selectedGameServiceConfiguration == null ? null :
+                            selectedGameServiceConfiguration.getPackageName();
+            final GameServiceComponentConfiguration gameServiceComponentConfiguration =
+                    selectedGameServiceConfiguration == null ? null
+                            : selectedGameServiceConfiguration
+                                    .getGameServiceComponentConfiguration();
 
-            boolean didActiveGameServiceProviderChanged =
-                    !Objects.equals(selectedGameServiceProviderConfiguration,
-                            mActiveGameServiceProviderConfiguration);
-            if (!didActiveGameServiceProviderChanged) {
+            evaluateGameServiceProviderPackageChangedListenerLocked(gameServicePackage);
+
+            boolean didActiveGameServiceProviderChange =
+                    !Objects.equals(gameServiceComponentConfiguration,
+                            mActiveGameServiceComponentConfiguration);
+            if (!didActiveGameServiceProviderChange) {
                 return;
             }
 
             if (mGameServiceProviderInstance != null) {
                 Slog.i(TAG, "Stopping Game Service provider: "
-                        + mActiveGameServiceProviderConfiguration);
+                        + mActiveGameServiceComponentConfiguration);
                 mGameServiceProviderInstance.stop();
+                mGameServiceProviderInstance = null;
             }
 
-            mActiveGameServiceProviderConfiguration = selectedGameServiceProviderConfiguration;
-
-            if (mActiveGameServiceProviderConfiguration == null) {
+            mActiveGameServiceComponentConfiguration = gameServiceComponentConfiguration;
+            if (mActiveGameServiceComponentConfiguration == null) {
                 return;
             }
 
             Slog.i(TAG,
-                    "Starting Game Service provider: " + mActiveGameServiceProviderConfiguration);
+                    "Starting Game Service provider: " + mActiveGameServiceComponentConfiguration);
             mGameServiceProviderInstance =
                     mGameServiceProviderInstanceFactory.create(
-                            mActiveGameServiceProviderConfiguration);
+                            mActiveGameServiceComponentConfiguration);
             mGameServiceProviderInstance.start();
         }
     }
+
+    @GuardedBy("mLock")
+    private void evaluateGameServiceProviderPackageChangedListenerLocked(
+            @Nullable String gameServicePackage) {
+        if (TextUtils.equals(mActiveGameServiceProviderPackage, gameServicePackage)) {
+            return;
+        }
+
+        if (mGameServicePackageChangedReceiver != null) {
+            mContext.unregisterReceiver(mGameServicePackageChangedReceiver);
+            mGameServicePackageChangedReceiver = null;
+        }
+
+        mActiveGameServiceProviderPackage = gameServicePackage;
+
+        if (TextUtils.isEmpty(mActiveGameServiceProviderPackage)) {
+            return;
+        }
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        intentFilter.addDataSchemeSpecificPart(gameServicePackage, PatternMatcher.PATTERN_LITERAL);
+        mGameServicePackageChangedReceiver = new PackageChangedBroadcastReceiver(
+                gameServicePackage);
+        mContext.registerReceiver(
+                mGameServicePackageChangedReceiver,
+                intentFilter);
+    }
+
+    private final class PackageChangedBroadcastReceiver extends BroadcastReceiver {
+        private final String mPackageName;
+
+        PackageChangedBroadcastReceiver(String packageName) {
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!TextUtils.equals(intent.getData().getSchemeSpecificPart(), mPackageName)) {
+                return;
+            }
+            mBackgroundExecutor.execute(
+                    GameServiceController.this::evaluateActiveGameServiceProvider);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java
deleted file mode 100644
index 7c8f251..0000000
--- a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.app;
-
-import android.annotation.NonNull;
-import android.content.ComponentName;
-import android.os.UserHandle;
-
-import java.util.Objects;
-
-/**
- * Representation of a {@link android.service.games.GameService} provider configuration.
- */
-final class GameServiceProviderConfiguration {
-    private final UserHandle mUserHandle;
-    private final ComponentName mGameServiceComponentName;
-    private final ComponentName mGameSessionServiceComponentName;
-
-    GameServiceProviderConfiguration(
-            @NonNull UserHandle userHandle,
-            @NonNull ComponentName gameServiceComponentName,
-            @NonNull ComponentName gameSessionServiceComponentName) {
-        Objects.requireNonNull(userHandle);
-        Objects.requireNonNull(gameServiceComponentName);
-        Objects.requireNonNull(gameSessionServiceComponentName);
-
-        this.mUserHandle = userHandle;
-        this.mGameServiceComponentName = gameServiceComponentName;
-        this.mGameSessionServiceComponentName = gameSessionServiceComponentName;
-    }
-
-    @NonNull
-    public UserHandle getUserHandle() {
-        return mUserHandle;
-    }
-
-    @NonNull
-    public ComponentName getGameServiceComponentName() {
-        return mGameServiceComponentName;
-    }
-
-    @NonNull
-    public ComponentName getGameSessionServiceComponentName() {
-        return mGameSessionServiceComponentName;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof GameServiceProviderConfiguration)) {
-            return false;
-        }
-
-        GameServiceProviderConfiguration that = (GameServiceProviderConfiguration) o;
-        return mUserHandle.equals(that.mUserHandle)
-                && mGameServiceComponentName.equals(that.mGameServiceComponentName)
-                && mGameSessionServiceComponentName.equals(that.mGameSessionServiceComponentName);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mUserHandle, mGameServiceComponentName,
-                mGameSessionServiceComponentName);
-    }
-
-    @Override
-    public String toString() {
-        return "GameServiceProviderConfiguration{"
-                + "mUserHandle="
-                + mUserHandle
-                + ", gameServiceComponentName="
-                + mGameServiceComponentName
-                + ", gameSessionServiceComponentName="
-                + mGameSessionServiceComponentName
-                + '}';
-    }
-}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java
index 7640cc5..7dfaec0 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java
@@ -18,12 +18,13 @@
 
 import android.annotation.NonNull;
 
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
+
 /**
  * Factory for creating {@link GameServiceProviderInstance}.
  */
 interface GameServiceProviderInstanceFactory {
 
     @NonNull
-    GameServiceProviderInstance create(@NonNull
-            GameServiceProviderConfiguration gameServiceProviderConfiguration);
+    GameServiceProviderInstance create(@NonNull GameServiceComponentConfiguration configuration);
 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
index 73278e4..0abab6a 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -30,6 +30,7 @@
 import com.android.internal.infra.ServiceConnector;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerService;
 
@@ -43,9 +44,9 @@
     @NonNull
     @Override
     public GameServiceProviderInstance create(
-            @NonNull GameServiceProviderConfiguration gameServiceProviderConfiguration) {
+            @NonNull GameServiceComponentConfiguration configuration) {
         return new GameServiceProviderInstanceImpl(
-                gameServiceProviderConfiguration.getUserHandle(),
+                configuration.getUserHandle(),
                 BackgroundThread.getExecutor(),
                 mContext,
                 new GameClassifierImpl(mContext.getPackageManager()),
@@ -53,8 +54,8 @@
                 ActivityTaskManager.getService(),
                 (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
                 LocalServices.getService(WindowManagerInternal.class),
-                new GameServiceConnector(mContext, gameServiceProviderConfiguration),
-                new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration));
+                new GameServiceConnector(mContext, configuration),
+                new GameSessionServiceConnector(mContext, configuration));
     }
 
     private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> {
@@ -63,7 +64,7 @@
 
         GameServiceConnector(
                 @NonNull Context context,
-                @NonNull GameServiceProviderConfiguration configuration) {
+                @NonNull GameServiceComponentConfiguration configuration) {
             super(context, new Intent(GameService.ACTION_GAME_SERVICE)
                             .setComponent(configuration.getGameServiceComponentName()),
                     BINDING_FLAGS, configuration.getUserHandle().getIdentifier(),
@@ -86,7 +87,7 @@
 
         GameSessionServiceConnector(
                 @NonNull Context context,
-                @NonNull GameServiceProviderConfiguration configuration) {
+                @NonNull GameServiceComponentConfiguration configuration) {
             super(context, new Intent(GameSessionService.ACTION_GAME_SESSION_SERVICE)
                             .setComponent(configuration.getGameSessionServiceComponentName()),
                     BINDING_FLAGS, configuration.getUserHandle().getIdentifier(),
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 4eba771..e8d9dad 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -244,11 +244,11 @@
 
         // TODO(b/204503192): It is possible that the game service is disconnected. In this
         //  case we should avoid rebinding just to shut it down again.
-        AndroidFuture<Void> unusedPostDisconnectedFuture =
-                mGameServiceConnector.post(gameService -> {
-                    gameService.disconnected();
-                });
-        mGameServiceConnector.unbind();
+        mGameServiceConnector.post(gameService -> {
+            gameService.disconnected();
+        }).whenComplete((result, t) -> {
+            mGameServiceConnector.unbind();
+        });
         mGameSessionServiceConnector.unbind();
     }
 
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelector.java b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
index 0f55b9f..d125f21 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderSelector.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelector.java
@@ -26,10 +26,10 @@
 interface GameServiceProviderSelector {
 
     /**
-     * Returns the {@link GameServiceProviderConfiguration} associated with the selected Game
+     * Returns the {@link GameServiceConfiguration} associated with the selected Game
      * Service provider for the given user or {@code null} if none should be used.
      */
     @Nullable
-    GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user,
+    GameServiceConfiguration get(@Nullable SystemService.TargetUser user,
             @Nullable String packageNameOverride);
 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
index c1ad668..fc85308 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java
@@ -34,6 +34,7 @@
 import android.util.Xml;
 
 import com.android.server.SystemService;
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -57,7 +58,7 @@
 
     @Override
     @Nullable
-    public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user,
+    public GameServiceConfiguration get(@Nullable SystemService.TargetUser user,
             @Nullable String packageNameOverride) {
         if (user == null) {
             return null;
@@ -98,10 +99,10 @@
 
         if (gameServiceResolveInfos == null || gameServiceResolveInfos.isEmpty()) {
             Slog.w(TAG, "No available game service found for user id: " + userId);
-            return null;
+            return new GameServiceConfiguration(gameServicePackage, null);
         }
 
-        GameServiceProviderConfiguration selectedProvider = null;
+        GameServiceConfiguration selectedProvider = null;
         for (ResolveInfo resolveInfo : gameServiceResolveInfos) {
             if (resolveInfo.serviceInfo == null) {
                 continue;
@@ -115,16 +116,18 @@
             }
 
             selectedProvider =
-                    new GameServiceProviderConfiguration(
-                            new UserHandle(userId),
-                            gameServiceServiceInfo.getComponentName(),
-                            gameSessionServiceComponentName);
+                    new GameServiceConfiguration(
+                            gameServicePackage,
+                            new GameServiceComponentConfiguration(
+                                    new UserHandle(userId),
+                                    gameServiceServiceInfo.getComponentName(),
+                                    gameSessionServiceComponentName));
             break;
         }
 
         if (selectedProvider == null) {
             Slog.w(TAG, "No valid game service found for user id: " + userId);
-            return null;
+            return new GameServiceConfiguration(gameServicePackage, null);
         }
 
         return selectedProvider;
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 3491cd5..49a935e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -479,6 +479,8 @@
         }
         if (profile == BluetoothProfile.A2DP) {
             mA2dp = (BluetoothA2dp) proxy;
+        } else if (profile == BluetoothProfile.HEARING_AID) {
+            mHearingAid = (BluetoothHearingAid) proxy;
         } else if (profile == BluetoothProfile.LE_AUDIO) {
             mLeAudio = (BluetoothLeAudio) proxy;
         }
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index eb2f80b..9f46bd6 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -389,7 +389,7 @@
             final long oldIdentity = Binder.clearCallingIdentity();
             try {
                 if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
-                        PROPERTY_AUTO_CLEAR_ENABLED, false)) {
+                        PROPERTY_AUTO_CLEAR_ENABLED, true)) {
                     mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
                             userId);
                     Message clearMessage = Message.obtain(mClipboardClearHandler,
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ea054a5..682f0df 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2020,7 +2020,8 @@
                             .setCategory(Notification.CATEGORY_SYSTEM)
                             .setVisibility(Notification.VISIBILITY_PUBLIC)
                             .setOngoing(true)
-                            .setColor(mContext.getColor(R.color.system_notification_accent_color));
+                            .setColor(mContext.getColor(
+                                    android.R.color.system_notification_accent_color));
             notificationManager.notify(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, builder.build());
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b2f500a..d2d80ff 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,6 +408,11 @@
     @GuardedBy("ImfLock.class")
     @NonNull
     InputBindResult bindCurrentMethod() {
+        if (mSelectedMethodId == null) {
+            Slog.e(TAG, "mSelectedMethodId is null!");
+            return InputBindResult.NO_IME;
+        }
+
         InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
         if (info == null) {
             throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 0c3f9f0..45d9822 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1428,6 +1428,7 @@
         ipw.println("Location Settings:");
         ipw.increaseIndent();
         mInjector.getSettingsHelper().dump(fd, ipw, args);
+        mInjector.getLocationSettings().dump(fd, ipw, args);
         ipw.decreaseIndent();
 
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 8fdde24..e9bf90f 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -16,8 +16,6 @@
 
 package com.android.server.location.contexthub;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import android.Manifest;
 import android.content.Context;
 import android.hardware.contexthub.V1_0.AsyncEventType;
@@ -297,19 +295,14 @@
     }
 
     /**
-     * Checks for location hardware permissions.
+     * Checks for ACCESS_CONTEXT_HUB permissions.
      *
      * @param context the context of the service
      */
     /* package */
     static void checkPermissions(Context context) {
-        boolean hasAccessContextHubPermission = (context.checkCallingPermission(
-                CONTEXT_HUB_PERMISSION) == PERMISSION_GRANTED);
-
-        if (!hasAccessContextHubPermission) {
-            throw new SecurityException(
-                    "ACCESS_CONTEXT_HUB permission required to use Context Hub");
-        }
+        context.enforceCallingOrSelfPermission(CONTEXT_HUB_PERMISSION,
+                "ACCESS_CONTEXT_HUB permission required to use Context Hub");
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/settings/LocationSettings.java b/services/core/java/com/android/server/location/settings/LocationSettings.java
index d521538..be0e7ac 100644
--- a/services/core/java/com/android/server/location/settings/LocationSettings.java
+++ b/services/core/java/com/android/server/location/settings/LocationSettings.java
@@ -18,8 +18,11 @@
 
 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Environment;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -29,6 +32,7 @@
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
@@ -104,6 +108,33 @@
         getUserSettingsStore(userId).update(updater);
     }
 
+    /** Dumps info for debugging. */
+    public final void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+        int[] userIds;
+        try {
+            userIds = ActivityManager.getService().getRunningUserIds();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
+            ipw.print("ADAS Location Setting: ");
+            ipw.increaseIndent();
+            if (userIds.length > 1) {
+                ipw.println();
+                for (int userId : userIds) {
+                    ipw.print("[u");
+                    ipw.print(userId);
+                    ipw.print("] ");
+                    ipw.println(getUserSettings(userId).isAdasGnssLocationEnabled());
+                }
+            } else {
+                ipw.println(getUserSettings(userIds[0]).isAdasGnssLocationEnabled());
+            }
+            ipw.decreaseIndent();
+        }
+    }
+
     @VisibleForTesting
     final void flushFiles() throws InterruptedException {
         synchronized (mUserSettings) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 96391ac..074d891 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -647,18 +647,17 @@
             if (mDestroyed) {
                 return;
             }
-            toSend = new ArrayList<>();
-            if (mQueue != null) {
-                toSend.ensureCapacity(mQueue.size());
-                toSend.addAll(mQueue);
-            }
+            toSend = mQueue == null ? null : new ArrayList<>(mQueue);
         }
         Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
         for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
-            ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend);
-            // Limit the size of initial Parcel to prevent binder buffer overflow
-            // as onQueueChanged is an async binder call.
-            parcelableQueue.setInlineCountLimit(1);
+            ParceledListSlice<QueueItem> parcelableQueue = null;
+            if (toSend != null) {
+                parcelableQueue = new ParceledListSlice<>(toSend);
+                // Limit the size of initial Parcel to prevent binder buffer overflow
+                // as onQueueChanged is an async binder call.
+                parcelableQueue.setInlineCountLimit(1);
+            }
 
             try {
                 holder.mCallback.onQueueChanged(parcelableQueue);
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 851ea3d..1b7d1ba 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -293,7 +293,7 @@
                         .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
                                 mResetIntent)
                         .setColor(mContext.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
+                                android.R.color.system_notification_accent_color));
 
         mNotificationManager.notify(null /* tag */, SystemMessage.NOTE_VPN_STATUS,
                 builder.build());
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 0052df3..60962b1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4157,7 +4157,7 @@
         if (mRestrictedNetworkingMode) {
             // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
             // In this case, default firewall rules can also be added.
-            setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
+            setUidFirewallRuleUL(FIREWALL_CHAIN_RESTRICTED, uid,
                     getRestrictedModeFirewallRule(uidBlockedState));
         }
     }
@@ -4321,10 +4321,10 @@
                 && (uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY)
                 == 0) {
             mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
-            setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
+            setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
         } else {
             mUidFirewallLowPowerStandbyModeRules.delete(uid);
-            setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+            setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
         }
     }
 
@@ -4373,9 +4373,9 @@
             final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid,
                     chain == FIREWALL_CHAIN_DOZABLE);
             if (isWhitelisted || isUidForegroundOnRestrictPowerUL(uid)) {
-                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
+                setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_ALLOW);
             } else {
-                setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
+                setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_DEFAULT);
             }
         }
     }
@@ -4421,10 +4421,10 @@
             int appId = UserHandle.getAppId(uid);
             if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
                     && !isUidForegroundOnRestrictPowerUL(uid)) {
-                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+                setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
                 if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL DENY " + uid);
             } else {
-                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+                setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
                 if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL " + uid + " to DEFAULT");
             }
         } finally {
@@ -5495,10 +5495,11 @@
     /**
      * Add or remove a uid to the firewall denylist for all network ifaces.
      */
-    private void setUidFirewallRule(int chain, int uid, int rule) {
+    @GuardedBy("mUidRulesFirstLock")
+    private void setUidFirewallRuleUL(int chain, int uid, int rule) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
-                    "setUidFirewallRule: " + chain + "/" + uid + "/" + rule);
+                    "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
         }
         try {
             if (chain == FIREWALL_CHAIN_DOZABLE) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9e87898..30ac1b8 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3134,8 +3134,8 @@
                 writer.println("Domain verification status:");
                 writer.increaseIndent();
                 try {
-                    mDomainVerificationManager.printState(writer, packageName,
-                            UserHandle.USER_ALL, mSettings::getPackage);
+                    mDomainVerificationManager.printState(this, writer, packageName,
+                            UserHandle.USER_ALL);
                 } catch (Exception e) {
                     pw.println("Failure printing domain verification information");
                     Slog.e(TAG, "Failure printing domain verification information", e);
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 3220b31..58f9bb2c 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -453,7 +453,8 @@
         }
         for (final int affectedUserId : affectedUserIds) {
             if (hadSuspendAppsPermission.get(affectedUserId)) {
-                mPm.unsuspendForSuspendingPackage(packageName, affectedUserId);
+                mPm.unsuspendForSuspendingPackage(mPm.snapshotComputer(), packageName,
+                        affectedUserId);
                 mPm.removeAllDistractingPackageRestrictions(affectedUserId);
             }
         }
diff --git a/services/core/java/com/android/server/pm/DomainVerificationConnection.java b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
index d24435e..db8c6dc 100644
--- a/services/core/java/com/android/server/pm/DomainVerificationConnection.java
+++ b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
@@ -26,17 +26,12 @@
 import android.os.Message;
 import android.os.UserHandle;
 
-import com.android.internal.util.FunctionalUtils;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2;
 
-import java.util.function.Consumer;
-import java.util.function.Function;
-
 public final class DomainVerificationConnection implements DomainVerificationService.Connection,
         DomainVerificationProxyV1.Connection, DomainVerificationProxyV2.Connection {
     final PackageManagerService mPm;
@@ -111,42 +106,8 @@
         return mUmInternal.exists(userId);
     }
 
-    @Override
-    public void withPackageSettingsSnapshot(
-            @NonNull Consumer<Function<String, PackageStateInternal>> block) {
-        mPmInternal.withPackageSettingsSnapshot(block);
-    }
-
-    @Override
-    public <Output> Output withPackageSettingsSnapshotReturning(
-            @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>,
-                    Output> block) {
-        return mPmInternal.withPackageSettingsSnapshotReturning(block);
-    }
-
-    @Override
-    public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
-            @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageStateInternal>,
-                    ExceptionType> block) throws ExceptionType {
-        mPmInternal.withPackageSettingsSnapshotThrowing(block);
-    }
-
-    @Override
-    public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
-            withPackageSettingsSnapshotThrowing2(
-                    @NonNull FunctionalUtils.ThrowingChecked2Consumer<
-                            Function<String, PackageStateInternal>, ExceptionOne,
-                            ExceptionTwo> block)
-            throws ExceptionOne, ExceptionTwo {
-        mPmInternal.withPackageSettingsSnapshotThrowing2(block);
-    }
-
-    @Override
-    public <Output, ExceptionType extends Exception> Output
-            withPackageSettingsSnapshotReturningThrowing(
-            @NonNull FunctionalUtils.ThrowingCheckedFunction<
-                    Function<String, PackageStateInternal>, Output, ExceptionType> block)
-            throws ExceptionType {
-        return mPmInternal.withPackageSettingsSnapshotReturningThrowing(block);
+    @NonNull
+    public Computer snapshot() {
+        return (Computer) mPmInternal.snapshot();
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 11e731a..db0b0c58 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -144,6 +144,7 @@
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.pm.dex.ArtManagerService;
@@ -2559,7 +2560,9 @@
         Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
 
         synchronized (mPm.mLock) {
-            size = mPm.mPendingBroadcasts.size();
+            final SparseArray<ArrayMap<String, ArrayList<String>>> userIdToPackagesToComponents =
+                    mPm.mPendingBroadcasts.copiedMap();
+            size = userIdToPackagesToComponents.size();
             if (size <= 0) {
                 // Nothing to be done. Just return
                 return;
@@ -2569,11 +2572,11 @@
             uids = new int[size];
             int i = 0;  // filling out the above arrays
 
-            for (int n = 0; n < mPm.mPendingBroadcasts.userIdCount(); n++) {
-                final int packageUserId = mPm.mPendingBroadcasts.userIdAt(n);
+            for (int n = 0; n < size; n++) {
+                final int packageUserId = userIdToPackagesToComponents.keyAt(n);
                 final ArrayMap<String, ArrayList<String>> componentsToBroadcast =
-                        mPm.mPendingBroadcasts.packagesForUserId(packageUserId);
-                final int numComponents = componentsToBroadcast.size();
+                        userIdToPackagesToComponents.valueAt(n);
+                final int numComponents = CollectionUtils.size(componentsToBroadcast);
                 for (int index = 0; i < size && index < numComponents; index++) {
                     packages[i] = componentsToBroadcast.keyAt(index);
                     components[i] = componentsToBroadcast.valueAt(index);
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 46e2aa3..b028a2c 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -382,6 +382,7 @@
             case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: {
                 try {
                     mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries(
+                            mPm.snapshotComputer(),
                             Long.MAX_VALUE,
                             Settings.Global.getLong(mPm.mContext.getContentResolver(),
                                     Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0b7a6a7..c05faf1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -293,7 +293,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Keep track of all those APKs everywhere.
@@ -1898,7 +1897,8 @@
             t.traceEnd();
 
             t.traceBegin("read user settings");
-            mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(
+            mFirstBoot = !mSettings.readLPw(mLiveComputer,
+                    mInjector.getUserManagerInternal().getUsers(
                     /* excludePartial= */ true,
                     /* excludeDying= */ false,
                     /* excludePreCreated= */ false));
@@ -2841,8 +2841,9 @@
             }
             if (file.getUsableSpace() >= bytes) return;
 
+            Computer computer = snapshotComputer();
             // 5. Consider shared libraries with refcount=0 and age>min cache period
-            if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(bytes,
+            if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(computer, bytes,
                     android.provider.Settings.Global.getLong(mContext.getContentResolver(),
                             Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
                             FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) {
@@ -2854,14 +2855,12 @@
 
             // 7. Consider installed instant apps unused longer than min cache period
             if (internalVolume) {
-                if (executeWithConsistentComputerReturning(computer ->
-                        mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
-                                android.provider.Settings.Global.getLong(
-                                        mContext.getContentResolver(),
-                                        Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                                        InstantAppRegistry
-                                                .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
-                ) {
+                if (mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes,
+                        android.provider.Settings.Global.getLong(
+                                mContext.getContentResolver(),
+                                Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                                InstantAppRegistry
+                                        .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
                     return;
                 }
             }
@@ -2881,14 +2880,12 @@
 
             // 10. Consider instant meta-data (uninstalled apps) older that min cache period
             if (internalVolume) {
-                if (executeWithConsistentComputerReturning(computer ->
-                        mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
-                                android.provider.Settings.Global.getLong(
-                                        mContext.getContentResolver(),
-                                        Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                                        InstantAppRegistry
-                                                .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD)))
-                ) {
+                if (mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes,
+                        android.provider.Settings.Global.getLong(
+                                mContext.getContentResolver(),
+                                Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                                InstantAppRegistry
+                                        .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
                     return;
                 }
             }
@@ -3493,8 +3490,8 @@
         enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */,
                 false /* checkShell */, "getEphemeralApplications");
 
-        List<InstantAppInfo> instantApps = executeWithConsistentComputerReturning(computer ->
-                mInstantAppRegistry.getInstantApps(computer, userId));
+        Computer computer = snapshotComputer();
+        List<InstantAppInfo> instantApps = mInstantAppRegistry.getInstantApps(computer, userId);
         if (instantApps != null) {
             return new ParceledListSlice<>(instantApps);
         }
@@ -3710,13 +3707,13 @@
     public void notifyPackageUse(String packageName, int reason) {
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
-        boolean notify = executeWithConsistentComputerReturning(computer -> {
-            if (getInstantAppPackageName(callingUid) != null) {
-                return isCallerSameApp(packageName, callingUid);
-            } else {
-                return !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID);
-            }
-        });
+        Computer computer = snapshotComputer();
+        final boolean notify;
+        if (getInstantAppPackageName(callingUid) != null) {
+            notify = isCallerSameApp(packageName, callingUid);
+        } else {
+            notify = !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID);
+        }
         if (!notify) {
             return;
         }
@@ -4255,34 +4252,33 @@
         final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
 
         ArraySet<String> changesToCommit = new ArraySet<>();
-        executeWithConsistentComputer(computer -> {
-            final boolean[] canRestrict = (restrictionFlags != 0)
-                    ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId,
-                    callingUid) : null;
-            for (int i = 0; i < packageNames.length; i++) {
-                final String packageName = packageNames[i];
-                final PackageStateInternal packageState =
-                        computer.getPackageStateInternal(packageName);
-                if (packageState == null
-                        || shouldFilterApplication(packageState, callingUid, userId)) {
-                    Slog.w(TAG, "Could not find package setting for package: " + packageName
-                            + ". Skipping...");
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                if (canRestrict != null && !canRestrict[i]) {
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-                final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
-                        .getDistractionFlags();
-                if (restrictionFlags != oldDistractionFlags) {
-                    changedPackagesList.add(packageName);
-                    changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
-                    changesToCommit.add(packageName);
-                }
+        Computer computer = snapshotComputer();
+        final boolean[] canRestrict = (restrictionFlags != 0)
+                ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId,
+                callingUid) : null;
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            final PackageStateInternal packageState =
+                    computer.getPackageStateInternal(packageName);
+            if (packageState == null
+                    || computer.shouldFilterApplication(packageState, callingUid, userId)) {
+                Slog.w(TAG, "Could not find package setting for package: " + packageName
+                        + ". Skipping...");
+                unactionedPackages.add(packageName);
+                continue;
             }
-        });
+            if (canRestrict != null && !canRestrict[i]) {
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            final int oldDistractionFlags = packageState.getUserStateOrDefault(userId)
+                    .getDistractionFlags();
+            if (restrictionFlags != oldDistractionFlags) {
+                changedPackagesList.add(packageName);
+                changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                changesToCommit.add(packageName);
+            }
+        }
 
         commitPackageStateMutation(null, mutator -> {
             final int size = changesToCommit.size();
@@ -4341,8 +4337,9 @@
         final int callingUid = Binder.getCallingUid();
         enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId,
                 "setPackagesSuspendedAsUser");
-        return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras,
-                launcherExtras, dialogInfo, callingPackage, userId, callingUid);
+        return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames,
+                suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId,
+                callingUid);
     }
 
     @Override
@@ -4361,11 +4358,12 @@
         return mComputer.isPackageSuspendedForUser(packageName, userId);
     }
 
-    void unsuspendForSuspendingPackage(String suspendingPackage, int userId) {
+    void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
+            @UserIdInt int userId) {
         // TODO: This can be replaced by a special parameter to iterate all packages, rather than
         //  this weird pre-collect of all packages.
-        final String[] allPackages = getPackageStates().keySet().toArray(new String[0]);
-        mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
+        final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
+        mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
                 allPackages, suspendingPackage::equals, userId);
     }
 
@@ -4424,7 +4422,7 @@
             throw new SecurityException("Calling uid " + callingUid
                     + " cannot query getUnsuspendablePackagesForUser for user " + userId);
         }
-        return mSuspendPackageHelper.getUnsuspendablePackagesForUser(
+        return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
                 packageNames, userId, callingUid);
     }
 
@@ -4625,7 +4623,7 @@
             return true;
         };
         PackageStateMutator.InitialState initialState = recordInitialState();
-        boolean allowed = executeWithConsistentComputerReturningThrowing(implementation);
+        boolean allowed = implementation.apply(snapshotComputer());
         if (allowed) {
             // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames,
             //  should find an alternative which avoids any race conditions
@@ -4635,7 +4633,7 @@
                         targetPackage, state -> state.setInstaller(installerPackageName));
                 if (result.isPackagesChanged() || result.isStateChanged()) {
                     synchronized (mPackageStateWriteLock) {
-                        allowed = executeWithConsistentComputerReturningThrowing(implementation);
+                        allowed = implementation.apply(snapshotComputer());
                         if (allowed) {
                             commitPackageStateMutation(null, targetPackage,
                                     state -> state.setInstaller(installerPackageName));
@@ -4685,12 +4683,12 @@
             }
         };
 
-        PackageStateMutator.Result result = executeWithConsistentComputerReturning(implementation);
+        PackageStateMutator.Result result = implementation.apply(snapshotComputer());
         if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) {
             // TODO: Specific return value of what state changed?
             // The installer on record might have changed, retry with lock
             synchronized (mPackageStateWriteLock) {
-                result = executeWithConsistentComputerReturning(implementation);
+                result = implementation.apply(snapshotComputer());
             }
         }
 
@@ -4988,7 +4986,7 @@
                     }
                     if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
                             == PERMISSION_GRANTED) {
-                        unsuspendForSuspendingPackage(packageName, userId);
+                        unsuspendForSuspendingPackage(snapshotComputer(), packageName, userId);
                         removeAllDistractingPackageRestrictions(userId);
                         flushPackageRestrictionsAsUserInternalLocked(userId);
                     }
@@ -5080,17 +5078,7 @@
         updateInstantAppInstallerLocked(packageName);
         scheduleWritePackageRestrictions(userId);
 
-        final ArrayList<String> pendingComponents = mPendingBroadcasts.get(userId, packageName);
-        if (pendingComponents == null) {
-            mPendingBroadcasts.put(userId, packageName, updatedComponents);
-        } else {
-            for (int i = 0; i < updatedComponents.size(); i++) {
-                final String updatedComponent = updatedComponents.get(i);
-                if (!pendingComponents.contains(updatedComponent)) {
-                    pendingComponents.add(updatedComponent);
-                }
-            }
-        }
+        mPendingBroadcasts.addComponents(userId, packageName, updatedComponents);
         if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
             mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
         }
@@ -5278,7 +5266,8 @@
         try {
             try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
                 TypedXmlSerializer serializer = Xml.resolveSerializer(output);
-                mDomainVerificationManager.writeSettings(serializer, true, userId);
+                mDomainVerificationManager.writeSettings(snapshotComputer(), serializer, true,
+                        userId);
                 return output.toByteArray();
             }
         } catch (Exception e) {
@@ -5301,7 +5290,7 @@
 
             // User ID input isn't necessary here as it assumes the user integers match and that
             // the only states inside the backup XML are for the target user.
-            mDomainVerificationManager.restoreSettings(parser);
+            mDomainVerificationManager.restoreSettings(snapshotComputer(), parser);
             input.close();
         } catch (Exception e) {
             if (DEBUG_BACKUP) {
@@ -5689,48 +5678,48 @@
         int callingUid = Binder.getCallingUid();
         String componentPkgName = componentName.getPackageName();
 
-        boolean changed = executeWithConsistentComputerReturning(computer -> {
-            int componentUid = getPackageUid(componentPkgName, 0, userId);
-            if (!UserHandle.isSameApp(callingUid, componentUid)) {
-                throw new SecurityException("The calling UID (" + callingUid + ")"
-                        + " does not match the target UID");
-            }
+        Computer computer = snapshotComputer();
 
-            String allowedCallerPkg =
-                    mContext.getString(R.string.config_overrideComponentUiPackage);
-            if (TextUtils.isEmpty(allowedCallerPkg)) {
-                throw new SecurityException( "There is no package defined as allowed to change a "
-                        + "component's label or icon");
-            }
+        int componentUid = computer.getPackageUid(componentPkgName, 0, userId);
+        if (!UserHandle.isSameApp(callingUid, componentUid)) {
+            throw new SecurityException("The calling UID (" + callingUid + ")"
+                    + " does not match the target UID");
+        }
 
-            int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
-                    userId);
-            if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
-                throw new SecurityException("The calling UID (" + callingUid + ")"
-                        + " is not allowed to change a component's label or icon");
-            }
-            PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName);
-            if (packageState == null || packageState.getPkg() == null
-                    || (!packageState.isSystem()
-                    && !packageState.getTransientState().isUpdatedSystemApp())) {
-                throw new SecurityException(
-                        "Changing the label is not allowed for " + componentName);
-            }
+        String allowedCallerPkg =
+                mContext.getString(R.string.config_overrideComponentUiPackage);
+        if (TextUtils.isEmpty(allowedCallerPkg)) {
+            throw new SecurityException( "There is no package defined as allowed to change a "
+                    + "component's label or icon");
+        }
 
-            if (!mComponentResolver.componentExists(componentName)) {
-                throw new IllegalArgumentException("Component " + componentName + " not found");
-            }
+        int allowedCallerUid = computer.getPackageUid(allowedCallerPkg,
+                PackageManager.MATCH_SYSTEM_ONLY, userId);
+        if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
+            throw new SecurityException("The calling UID (" + callingUid + ")"
+                    + " is not allowed to change a component's label or icon");
+        }
+        PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName);
+        if (packageState == null || packageState.getPkg() == null
+                || (!packageState.isSystem()
+                && !packageState.getTransientState().isUpdatedSystemApp())) {
+            throw new SecurityException(
+                    "Changing the label is not allowed for " + componentName);
+        }
 
-            Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId)
-                    .getOverrideLabelIconForComponent(componentName);
+        if (!computer.getComponentResolver().componentExists(componentName)) {
+            throw new IllegalArgumentException("Component " + componentName + " not found");
+        }
 
-            String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first;
-            Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second;
+        Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId)
+                .getOverrideLabelIconForComponent(componentName);
 
-            return !TextUtils.equals(existingLabel, nonLocalizedLabel)
-                    || !Objects.equals(existingIcon, icon);
-        });
-        if (!changed) {
+        String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first;
+        Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second;
+
+        if (TextUtils.equals(existingLabel, nonLocalizedLabel)
+                && Objects.equals(existingIcon, icon)) {
+            // Nothing changed
             return;
         }
 
@@ -5738,16 +5727,7 @@
                 state -> state.userState(userId)
                         .setComponentLabelIcon(componentName, nonLocalizedLabel, icon));
 
-        ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName);
-        if (components == null) {
-            components = new ArrayList<>();
-            mPendingBroadcasts.put(userId, componentPkgName, components);
-        }
-
-        String className = componentName.getClassName();
-        if (!components.contains(className)) {
-            components.add(className);
-        }
+        mPendingBroadcasts.addComponent(userId, componentPkgName, componentName.getClassName());
 
         if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
             mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
@@ -5956,6 +5936,7 @@
         // packageName -> list of components to send broadcasts now
         final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
         synchronized (mLock) {
+            Computer computer = snapshotComputer();
             boolean scheduleBroadcastMessage = false;
             boolean isSynchronous = false;
             boolean anyChanged = false;
@@ -5967,8 +5948,8 @@
                 // update enabled settings
                 final ComponentEnabledSetting setting = settings.get(i);
                 final String packageName = setting.getPackageName();
-                if (!setEnabledSettingInternalLocked(pkgSettings.get(packageName), setting,
-                        userId, callingPackage)) {
+                if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
+                        setting, userId, callingPackage)) {
                     continue;
                 }
                 anyChanged = true;
@@ -5979,26 +5960,18 @@
                 // collect broadcast list for the package
                 final String componentName = setting.isComponent()
                         ? setting.getClassName() : packageName;
-                ArrayList<String> componentList = sendNowBroadcasts.get(packageName);
-                if (componentList == null) {
-                    componentList = mPendingBroadcasts.get(userId, packageName);
-                }
-                final boolean newPackage = componentList == null;
-                if (newPackage) {
-                    componentList = new ArrayList<>();
-                }
-                if (!componentList.contains(componentName)) {
-                    componentList.add(componentName);
-                }
                 if ((setting.getEnabledFlags() & PackageManager.DONT_KILL_APP) == 0) {
+                    ArrayList<String> componentList = sendNowBroadcasts.get(packageName);
+                    componentList = componentList == null ? new ArrayList<>() : componentList;
+                    if (!componentList.contains(componentName)) {
+                        componentList.add(componentName);
+                    }
                     sendNowBroadcasts.put(packageName, componentList);
                     // Purge entry from pending broadcast list if another one exists already
                     // since we are sending one right away.
                     mPendingBroadcasts.remove(userId, packageName);
                 } else {
-                    if (newPackage) {
-                        mPendingBroadcasts.put(userId, packageName, componentList);
-                    }
+                    mPendingBroadcasts.addComponent(userId, packageName, componentName);
                     scheduleBroadcastMessage = true;
                 }
             }
@@ -6041,8 +6014,9 @@
         }
     }
 
-    private boolean setEnabledSettingInternalLocked(PackageSetting pkgSetting,
-            ComponentEnabledSetting setting, int userId, String callingPackage) {
+    private boolean setEnabledSettingInternalLocked(@NonNull Computer computer,
+            PackageSetting pkgSetting, ComponentEnabledSetting setting, @UserIdInt int userId,
+            String callingPackage) {
         final int newState = setting.getEnabledState();
         final String packageName = setting.getPackageName();
         boolean success = false;
@@ -6061,7 +6035,7 @@
                 // This app should not generally be allowed to get disabled by the UI, but
                 // if it ever does, we don't want to end up with some of the user's apps
                 // permanently suspended.
-                unsuspendForSuspendingPackage(packageName, userId);
+                unsuspendForSuspendingPackage(computer, packageName, userId);
                 removeAllDistractingPackageRestrictions(userId);
             }
             success = true;
@@ -6152,11 +6126,8 @@
     public void setPackageStoppedState(String packageName, boolean stopped, int userId) {
         if (!mUserManager.exists(userId)) return;
         final int callingUid = Binder.getCallingUid();
-        Pair<Boolean, String> wasNotLaunchedAndInstallerPackageName =
-                executeWithConsistentComputerReturningThrowing(computer -> {
-            if (computer.getInstantAppPackageName(callingUid) != null) {
-                return null;
-            }
+        final Computer computer = snapshotComputer();
+        if (computer.getInstantAppPackageName(callingUid) == null) {
             final int permission = mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
             final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
@@ -6171,36 +6142,30 @@
                     true /* requireFullPermission */, true /* checkShell */, "stop package");
 
             final PackageStateInternal packageState = computer.getPackageStateInternal(packageName);
-            final PackageUserState PackageUserState = packageState == null
+            final PackageUserState packageUserState = packageState == null
                     ? null : packageState.getUserStateOrDefault(userId);
-            if (packageState == null
-                    || computer.shouldFilterApplication(packageState, callingUid, userId)
-                    || PackageUserState.isStopped() == stopped) {
-                return null;
-            }
+            if (packageState != null
+                    && !computer.shouldFilterApplication(packageState, callingUid, userId)
+                    && packageUserState.isStopped() != stopped) {
+                boolean wasNotLaunched = packageUserState.isNotLaunched();
+                commitPackageStateMutation(null, packageName, state -> {
+                    PackageUserStateWrite userState = state.userState(userId);
+                    userState.setStopped(stopped);
+                    if (wasNotLaunched) {
+                        userState.setNotLaunched(false);
+                    }
+                });
 
-            return Pair.create(PackageUserState.isNotLaunched(),
-                    packageState.getInstallSource().installerPackageName);
-        });
-        if (wasNotLaunchedAndInstallerPackageName != null) {
-            boolean wasNotLaunched = wasNotLaunchedAndInstallerPackageName.first;
-
-            commitPackageStateMutation(null, packageName, packageState -> {
-                PackageUserStateWrite userState = packageState.userState(userId);
-                userState.setStopped(stopped);
                 if (wasNotLaunched) {
-                    userState.setNotLaunched(false);
+                    final String installerPackageName =
+                            packageState.getInstallSource().installerPackageName;
+                    if (installerPackageName != null) {
+                        notifyFirstLaunch(packageName, installerPackageName, userId);
+                    }
                 }
-            });
 
-            if (wasNotLaunched) {
-                final String installerPackageName = wasNotLaunchedAndInstallerPackageName.second;
-                if (installerPackageName != null) {
-                    notifyFirstLaunch(packageName, installerPackageName, userId);
-                }
+                scheduleWritePackageRestrictions(userId);
             }
-
-            scheduleWritePackageRestrictions(userId);
         }
 
         // If this would cause the app to leave force-stop, then also make sure to unhibernate the
@@ -6687,7 +6652,7 @@
                 "Only package verification agents can read the verifier device identity");
 
         synchronized (mLock) {
-            return mSettings.getVerifierDeviceIdentityLPw();
+            return mSettings.getVerifierDeviceIdentityLPw(mLiveComputer);
         }
     }
 
@@ -7026,15 +6991,16 @@
 
         @Override
         public void removeAllNonSystemPackageSuspensions(int userId) {
-            final String[] allPackages = mComputer.getAllAvailablePackageNames();
-            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages,
+            final Computer computer = snapshotComputer();
+            final String[] allPackages = computer.getAllAvailablePackageNames();
+            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages,
                     (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                     userId);
         }
 
         @Override
         public void removeNonSystemPackageSuspensions(String packageName, int userId) {
-            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
+            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(snapshotComputer(),
                     new String[]{packageName},
                     (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                     userId);
@@ -7233,29 +7199,29 @@
         @Override
         public void grantImplicitAccess(int userId, Intent intent,
                 int recipientAppId, int visibleUid, boolean direct, boolean retainOnUpdate) {
-            boolean accessGranted = executeWithConsistentComputerReturning(computer -> {
-                final AndroidPackage visiblePackage = computer.getPackage(visibleUid);
-                final int recipientUid = UserHandle.getUid(userId, recipientAppId);
-                if (visiblePackage == null || computer.getPackage(recipientUid) == null) {
-                    return false;
-                }
+            Computer computer = snapshotComputer();
+            final AndroidPackage visiblePackage = computer.getPackage(visibleUid);
+            final int recipientUid = UserHandle.getUid(userId, recipientAppId);
+            if (visiblePackage == null || computer.getPackage(recipientUid) == null) {
+                return;
+            }
 
-                final boolean instantApp = computer.isInstantAppInternal(
-                        visiblePackage.getPackageName(), userId, visibleUid);
-                if (instantApp) {
-                    if (!direct) {
-                        // if the interaction that lead to this granting access to an instant app
-                        // was indirect (i.e.: URI permission grant), do not actually execute the
-                        // grant.
-                        return false;
-                    }
-                    return mInstantAppRegistry.grantInstantAccess(userId, intent,
-                            recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
-                } else {
-                    return mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
-                            retainOnUpdate);
+            final boolean instantApp = computer.isInstantAppInternal(
+                    visiblePackage.getPackageName(), userId, visibleUid);
+            final boolean accessGranted;
+            if (instantApp) {
+                if (!direct) {
+                    // if the interaction that lead to this granting access to an instant app
+                    // was indirect (i.e.: URI permission grant), do not actually execute the
+                    // grant.
+                    return;
                 }
-            });
+                accessGranted = mInstantAppRegistry.grantInstantAccess(userId, intent,
+                        recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
+            } else {
+                accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid,
+                        retainOnUpdate);
+            }
 
             if (accessGranted) {
                 ApplicationPackageManager.invalidateGetPackagesForUidCache();
@@ -7271,8 +7237,7 @@
 
         @Override
         public void pruneInstantApps() {
-            executeWithConsistentComputer(computer ->
-                    mInstantAppRegistry.pruneInstantApps(computer));
+            mInstantAppRegistry.pruneInstantApps(snapshotComputer());
         }
 
         @Override
@@ -7680,7 +7645,8 @@
 
         @Override
         public void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
-            PackageManagerService.this.unsuspendForSuspendingPackage(packageName, affectedUser);
+            PackageManagerService.this.unsuspendForSuspendingPackage(snapshotComputer(),
+                    packageName, affectedUser);
         }
 
         @Override
@@ -7744,52 +7710,6 @@
         }
 
         @Override
-        public void withPackageSettingsSnapshot(
-                @NonNull Consumer<Function<String, PackageStateInternal>> block) {
-            executeWithConsistentComputer(computer ->
-                    block.accept(computer::getPackageStateInternal));
-        }
-
-        @Override
-        public <Output> Output withPackageSettingsSnapshotReturning(
-                @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>,
-                        Output> block) {
-            return executeWithConsistentComputerReturning(computer ->
-                    block.apply(computer::getPackageStateInternal));
-        }
-
-        @Override
-        public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
-                @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String,
-                        PackageStateInternal>, ExceptionType> block) throws ExceptionType {
-            executeWithConsistentComputerThrowing(computer ->
-                    block.accept(computer::getPackageStateInternal));
-        }
-
-        @Override
-        public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
-                withPackageSettingsSnapshotThrowing2(
-                        @NonNull FunctionalUtils.ThrowingChecked2Consumer<
-                                Function<String, PackageStateInternal>, ExceptionOne,
-                                ExceptionTwo> block)
-                throws ExceptionOne, ExceptionTwo {
-            executeWithConsistentComputerThrowing2(
-                    (FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
-                            ExceptionTwo>) computer -> block.accept(computer::getPackageStateInternal));
-        }
-
-        @Override
-        public <Output, ExceptionType extends Exception> Output
-                withPackageSettingsSnapshotReturningThrowing(
-                        @NonNull FunctionalUtils.ThrowingCheckedFunction<
-                                Function<String, PackageStateInternal>, Output,
-                                ExceptionType> block)
-                throws ExceptionType {
-            return executeWithConsistentComputerReturningThrowing(computer ->
-                    block.apply(computer::getPackageStateInternal));
-        }
-
-        @Override
         public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
                 boolean migrateAppsData) {
             PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags,
@@ -7834,70 +7754,59 @@
             @NonNull Set<String> outUpdatedPackageNames) {
         synchronized (mOverlayPathsLock) {
             final ArrayMap<String, ArraySet<String>> libNameToModifiedDependents = new ArrayMap<>();
-            Boolean targetModified = executeWithConsistentComputerReturning(computer -> {
-                final PackageStateInternal packageState = computer.getPackageStateInternal(
-                        targetPackageName);
-                final AndroidPackage targetPkg =
-                        packageState == null ? null : packageState.getPkg();
-                if (targetPackageName == null || targetPkg == null) {
-                    Slog.e(TAG, "failed to find package " + targetPackageName);
-                    return null;
-                }
-
-                if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
-                        newOverlayPaths)) {
-                    return false;
-                }
-
-                if (targetPkg.getLibraryNames() != null) {
-                    // Set the overlay paths for dependencies of the shared library.
-                    for (final String libName : targetPkg.getLibraryNames()) {
-                        ArraySet<String> modifiedDependents = null;
-
-                        final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName,
-                                SharedLibraryInfo.VERSION_UNDEFINED);
-                        if (info == null) {
-                            continue;
-                        }
-                        final List<VersionedPackage> dependents = computer
-                                .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId);
-                        if (dependents == null) {
-                            continue;
-                        }
-                        for (final VersionedPackage dependent : dependents) {
-                            final PackageStateInternal dependentState =
-                                    computer.getPackageStateInternal(dependent.getPackageName());
-                            if (dependentState == null) {
-                                continue;
-                            }
-                            if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
-                                    .getSharedLibraryOverlayPaths()
-                                    .get(libName), newOverlayPaths)) {
-                                String dependentPackageName = dependent.getPackageName();
-                                modifiedDependents = ArrayUtils.add(modifiedDependents,
-                                        dependentPackageName);
-                                outUpdatedPackageNames.add(dependentPackageName);
-                            }
-                        }
-
-                        if (modifiedDependents != null) {
-                            libNameToModifiedDependents.put(libName, modifiedDependents);
-                        }
-                    }
-                }
-
-                outUpdatedPackageNames.add(targetPackageName);
-                return true;
-            });
-
-            if (targetModified == null) {
-                // Null indicates error
+            Computer computer = snapshotComputer();
+            final PackageStateInternal packageState = computer.getPackageStateInternal(
+                    targetPackageName);
+            final AndroidPackage targetPkg = packageState == null ? null : packageState.getPkg();
+            if (targetPackageName == null || targetPkg == null) {
+                Slog.e(TAG, "failed to find package " + targetPackageName);
                 return false;
-            } else if (!targetModified) {
-                // Treat non-modification as a successful commit
+            }
+
+            if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+                    newOverlayPaths)) {
                 return true;
             }
 
+            if (targetPkg.getLibraryNames() != null) {
+                // Set the overlay paths for dependencies of the shared library.
+                for (final String libName : targetPkg.getLibraryNames()) {
+                    ArraySet<String> modifiedDependents = null;
+
+                    final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName,
+                            SharedLibraryInfo.VERSION_UNDEFINED);
+                    if (info == null) {
+                        continue;
+                    }
+                    final List<VersionedPackage> dependents = computer
+                            .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId);
+                    if (dependents == null) {
+                        continue;
+                    }
+                    for (final VersionedPackage dependent : dependents) {
+                        final PackageStateInternal dependentState =
+                                computer.getPackageStateInternal(dependent.getPackageName());
+                        if (dependentState == null) {
+                            continue;
+                        }
+                        if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+                                .getSharedLibraryOverlayPaths()
+                                .get(libName), newOverlayPaths)) {
+                            String dependentPackageName = dependent.getPackageName();
+                            modifiedDependents = ArrayUtils.add(modifiedDependents,
+                                    dependentPackageName);
+                            outUpdatedPackageNames.add(dependentPackageName);
+                        }
+                    }
+
+                    if (modifiedDependents != null) {
+                        libNameToModifiedDependents.put(libName, modifiedDependents);
+                    }
+                }
+            }
+
+            outUpdatedPackageNames.add(targetPackageName);
+
             commitPackageStateMutation(null, mutator -> {
                 mutator.forPackage(targetPackageName)
                         .userState(userId)
@@ -8062,36 +7971,6 @@
         forEachPackageState(mComputer.getPackageStates(), actionWrapped);
     }
 
-    // TODO: Make private
-    void executeWithConsistentComputer(
-            @NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) {
-        consumer.accept(snapshotComputer());
-    }
-
-    private <T> T executeWithConsistentComputerReturning(
-            @NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) {
-        return function.apply(snapshotComputer());
-    }
-
-    private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing(
-            @NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer)
-            throws ExceptionType {
-        consumer.accept(snapshotComputer());
-    }
-
-    private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
-    executeWithConsistentComputerThrowing2(
-            @NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne,
-                    ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo {
-        consumer.accept(snapshotComputer());
-    }
-
-    private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing(
-            @NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function)
-            throws ExceptionType {
-        return function.apply(snapshotComputer());
-    }
-
     boolean isHistoricalPackageUsageAvailable() {
         return mPackageUsage.isHistoricalPackageUsageAvailable();
     }
@@ -8357,7 +8236,7 @@
      */
     void writeSettingsLPrTEMP() {
         mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
-        mSettings.writeLPr();
+        mSettings.writeLPr(mLiveComputer);
     }
 
     @Override
@@ -8856,7 +8735,6 @@
     }
 
     void notifyInstantAppPackageInstalled(String packageName, int[] newUsers) {
-        executeWithConsistentComputer(computer ->
-                mInstantAppRegistry.onPackageInstalled(computer, packageName, newUsers));
+        mInstantAppRegistry.onPackageInstalled(snapshotComputer(), packageName, newUsers);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java b/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java
index 4e9a06a..6e2f756 100644
--- a/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java
+++ b/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java
@@ -16,12 +16,17 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Set of pending broadcasts for aggregating enable/disable of components.
@@ -29,65 +34,111 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class PendingPackageBroadcasts {
 
+    private final Object mLock = new PackageManagerTracedLock();
+
     // for each user id, a map of <package name -> components within that package>
+    @GuardedBy("mLock")
     final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap;
 
     public PendingPackageBroadcasts() {
         mUidMap = new SparseArray<>(2);
     }
 
-    public ArrayList<String> get(int userId, String packageName) {
-        ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId);
-        return packages.get(packageName);
+    public boolean hasPackage(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (mLock) {
+            final ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId);
+            return packages != null && packages.containsKey(packageName);
+        }
     }
 
     public void put(int userId, String packageName, ArrayList<String> components) {
-        ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId);
-        packages.put(packageName, components);
+        synchronized (mLock) {
+            ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId);
+            packages.put(packageName, components);
+        }
+    }
+
+    public void addComponent(@UserIdInt int userId, @NonNull String packageName,
+            @NonNull String componentClassName) {
+        synchronized (mLock) {
+            ArrayList<String> components = getOrAllocate(userId, packageName);
+            if (!components.contains(componentClassName)) {
+                components.add(componentClassName);
+            }
+        }
+    }
+
+    public void addComponents(@UserIdInt int userId, @NonNull String packageName,
+            @NonNull List<String> componentClassNames) {
+        synchronized (mLock) {
+            ArrayList<String> components = getOrAllocate(userId, packageName);
+            for (int index = 0; index < componentClassNames.size(); index++) {
+                String componentClassName = componentClassNames.get(index);
+                if (!components.contains(componentClassName)) {
+                    components.add(componentClassName);
+                }
+            }
+        }
     }
 
     public void remove(int userId, String packageName) {
-        ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId);
-        if (packages != null) {
-            packages.remove(packageName);
+        synchronized (mLock) {
+            ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId);
+            if (packages != null) {
+                packages.remove(packageName);
+            }
         }
     }
 
     public void remove(int userId) {
-        mUidMap.remove(userId);
-    }
-
-    public int userIdCount() {
-        return mUidMap.size();
-    }
-
-    public int userIdAt(int n) {
-        return mUidMap.keyAt(n);
-    }
-
-    public ArrayMap<String, ArrayList<String>> packagesForUserId(int userId) {
-        return mUidMap.get(userId);
-    }
-
-    public int size() {
-        // total number of pending broadcast entries across all userIds
-        int num = 0;
-        for (int i = 0; i < mUidMap.size(); i++) {
-            num += mUidMap.valueAt(i).size();
+        synchronized (mLock) {
+            mUidMap.remove(userId);
         }
-        return num;
+    }
+
+    @Nullable
+    public SparseArray<ArrayMap<String, ArrayList<String>>> copiedMap() {
+        synchronized (mLock) {
+            SparseArray<ArrayMap<String, ArrayList<String>>> copy = new SparseArray<>();
+            for (int userIdIndex = 0; userIdIndex < mUidMap.size(); userIdIndex++) {
+                final ArrayMap<String, ArrayList<String>> packages = mUidMap.valueAt(userIdIndex);
+                ArrayMap<String, ArrayList<String>> packagesCopy = new ArrayMap<>();
+                for (int packagesIndex = 0; packagesIndex < packages.size(); packagesIndex++) {
+                    packagesCopy.put(packages.keyAt(packagesIndex),
+                            new ArrayList<>(packages.valueAt(packagesIndex)));
+                }
+                copy.put(mUidMap.keyAt(userIdIndex), packagesCopy);
+            }
+            return copy;
+        }
     }
 
     public void clear() {
-        mUidMap.clear();
+        synchronized (mLock) {
+            mUidMap.clear();
+        }
     }
 
     private ArrayMap<String, ArrayList<String>> getOrAllocate(int userId) {
-        ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId);
-        if (map == null) {
-            map = new ArrayMap<>();
-            mUidMap.put(userId, map);
+        synchronized (mLock) {
+            ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId);
+            if (map == null) {
+                map = new ArrayMap<>();
+                mUidMap.put(userId, map);
+            }
+            return map;
         }
-        return map;
+    }
+
+    private ArrayList<String> getOrAllocate(int userId, @NonNull String packageName) {
+        synchronized (mLock) {
+            ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId);
+            if (map == null) {
+                map = new ArrayMap<>();
+                mUidMap.put(userId, map);
+            }
+
+            return map.computeIfAbsent(packageName, k -> new ArrayList<>());
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2ad35b7..394c8fb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2432,7 +2432,7 @@
         }
     }
 
-    void writeLPr() {
+    void writeLPr(@NonNull Computer computer) {
         //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024);
 
         final long startTime = SystemClock.uptimeMillis();
@@ -2524,8 +2524,8 @@
                 }
             }
 
-            mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */,
-                    UserHandle.USER_ALL);
+            mDomainVerificationManager.writeSettings(computer, serializer,
+                    false /* includeSignatures */, UserHandle.USER_ALL);
 
             mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
 
@@ -2967,7 +2967,7 @@
         }
     }
 
-    boolean readLPw(@NonNull List<UserInfo> users) {
+    boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
         FileInputStream str = null;
         if (mBackupSettingsFilename.exists()) {
             try {
@@ -3111,7 +3111,7 @@
                     ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
                     ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
                 } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
-                    mDomainVerificationManager.readSettings(parser);
+                    mDomainVerificationManager.readSettings(computer, parser);
                 } else if (tagName.equals(
                         DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
                     mDomainVerificationManager.readLegacySettings(parser);
@@ -4287,11 +4287,11 @@
         return Process.FIRST_APPLICATION_UID + size;
     }
 
-    public VerifierDeviceIdentity getVerifierDeviceIdentityLPw() {
+    public VerifierDeviceIdentity getVerifierDeviceIdentityLPw(@NonNull Computer computer) {
         if (mVerifierDeviceIdentity == null) {
             mVerifierDeviceIdentity = VerifierDeviceIdentity.generate();
 
-            writeLPr();
+            writeLPr(computer);
         }
 
         return mVerifierDeviceIdentity;
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 0638d5e..3fe0790 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -321,7 +321,8 @@
      *                       on the device.
      * @return {@code true} if the available storage space is reached.
      */
-    boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod)
+    boolean pruneUnusedStaticSharedLibraries(@NonNull Computer computer, long neededSpace,
+            long maxCachePeriod)
             throws IOException {
         final StorageManager storage = mInjector.getSystemService(StorageManager.class);
         final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
@@ -332,38 +333,36 @@
         // Important: We skip shared libs used for some user since
         // in such a case we need to keep the APK on the device. The check for
         // a lib being used for any user is performed by the uninstall call.
-        mPm.executeWithConsistentComputer(computer -> {
-            final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
-                    sharedLibraries = computer.getSharedLibraries();
-            final int libCount = sharedLibraries.size();
-            for (int i = 0; i < libCount; i++) {
-                final WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
-                        sharedLibraries.valueAt(i);
-                if (versionedLib == null) {
+        final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>
+                sharedLibraries = computer.getSharedLibraries();
+        final int libCount = sharedLibraries.size();
+        for (int i = 0; i < libCount; i++) {
+            final WatchedLongSparseArray<SharedLibraryInfo> versionedLib =
+                    sharedLibraries.valueAt(i);
+            if (versionedLib == null) {
+                continue;
+            }
+            final int versionCount = versionedLib.size();
+            for (int j = 0; j < versionCount; j++) {
+                SharedLibraryInfo libInfo = versionedLib.valueAt(j);
+                final PackageStateInternal ps = getLibraryPackage(computer, libInfo);
+                if (ps == null) {
                     continue;
                 }
-                final int versionCount = versionedLib.size();
-                for (int j = 0; j < versionCount; j++) {
-                    SharedLibraryInfo libInfo = versionedLib.valueAt(j);
-                    final PackageStateInternal ps = getLibraryPackage(computer, libInfo);
-                    if (ps == null) {
-                        continue;
-                    }
-                    // Skip unused libs cached less than the min period to prevent pruning a lib
-                    // needed by a subsequently installed package.
-                    if (now - ps.getLastUpdateTime() < maxCachePeriod) {
-                        continue;
-                    }
-
-                    if (ps.getPkg().isSystem()) {
-                        continue;
-                    }
-
-                    packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(),
-                            libInfo.getDeclaringPackage().getLongVersionCode()));
+                // Skip unused libs cached less than the min period to prevent pruning a lib
+                // needed by a subsequently installed package.
+                if (now - ps.getLastUpdateTime() < maxCachePeriod) {
+                    continue;
                 }
+
+                if (ps.getPkg().isSystem()) {
+                    continue;
+                }
+
+                packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(),
+                        libInfo.getDeclaringPackage().getLongVersionCode()));
             }
-        });
+        }
 
         final int packageCount = packagesToDelete.size();
         for (int i = 0; i < packageCount; i++) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index bd1c9c7..3ef5599 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Intent;
@@ -99,10 +100,10 @@
      * @return The names of failed packages.
      */
     @Nullable
-    String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
-            @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
-            @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage,
-            int userId, int callingUid) {
+    String[] setPackagesSuspended(@NonNull Computer computer, @Nullable String[] packageNames,
+            boolean suspended, @Nullable PersistableBundle appExtras,
+            @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo,
+            @NonNull String callingPackage, @UserIdInt int userId, int callingUid) {
         if (ArrayUtils.isEmpty(packageNames)) {
             return packageNames;
         }
@@ -121,62 +122,60 @@
 
         ArraySet<String> modifiedPackages = new ArraySet<>();
 
-        mPm.executeWithConsistentComputer(computer -> {
-            final boolean[] canSuspend = suspended
-                    ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null;
-            for (int i = 0; i < packageNames.length; i++) {
-                final String packageName = packageNames[i];
-                if (callingPackage.equals(packageName)) {
-                    Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
-                            + (suspended ? "" : "un") + "suspend itself. Ignoring");
-                    unmodifiablePackages.add(packageName);
-                    continue;
-                }
-                final PackageStateInternal packageState =
-                        computer.getPackageStateInternal(packageName);
-                if (packageState == null
-                        || computer.shouldFilterApplication(packageState, callingUid, userId)) {
-                    Slog.w(TAG, "Could not find package setting for package: " + packageName
-                            + ". Skipping suspending/un-suspending.");
-                    unmodifiablePackages.add(packageName);
-                    continue;
-                }
-                if (canSuspend != null && !canSuspend[i]) {
-                    unmodifiablePackages.add(packageName);
-                    continue;
-                }
+        final boolean[] canSuspend = suspended
+                ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null;
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            if (callingPackage.equals(packageName)) {
+                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+                        + (suspended ? "" : "un") + "suspend itself. Ignoring");
+                unmodifiablePackages.add(packageName);
+                continue;
+            }
+            final PackageStateInternal packageState =
+                    computer.getPackageStateInternal(packageName);
+            if (packageState == null
+                    || computer.shouldFilterApplication(packageState, callingUid, userId)) {
+                Slog.w(TAG, "Could not find package setting for package: " + packageName
+                        + ". Skipping suspending/un-suspending.");
+                unmodifiablePackages.add(packageName);
+                continue;
+            }
+            if (canSuspend != null && !canSuspend[i]) {
+                unmodifiablePackages.add(packageName);
+                continue;
+            }
 
-                final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
-                        packageState.getUserStateOrDefault(userId).getSuspendParams();
-                if (suspended) {
-                    if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) {
-                        final SuspendParams suspendParams = suspendParamsMap.get(packageName);
-                        // Skip if there's no changes
-                        if (suspendParams != null
-                                && Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
-                                && Objects.equals(suspendParams.getAppExtras(), appExtras)
-                                && Objects.equals(suspendParams.getLauncherExtras(),
-                                launcherExtras)) {
-                            // Carried over API behavior, must notify change even if no change
-                            changedPackagesList.add(packageName);
-                            changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
-                            continue;
-                        }
+            final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+                    packageState.getUserStateOrDefault(userId).getSuspendParams();
+            if (suspended) {
+                if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) {
+                    final SuspendParams suspendParams = suspendParamsMap.get(packageName);
+                    // Skip if there's no changes
+                    if (suspendParams != null
+                            && Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
+                            && Objects.equals(suspendParams.getAppExtras(), appExtras)
+                            && Objects.equals(suspendParams.getLauncherExtras(),
+                            launcherExtras)) {
+                        // Carried over API behavior, must notify change even if no change
+                        changedPackagesList.add(packageName);
+                        changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                        continue;
                     }
                 }
-
-                // If size one, the package will be unsuspended from this call
-                boolean packageUnsuspended =
-                        !suspended && CollectionUtils.size(suspendParamsMap) <= 1;
-                if (suspended || packageUnsuspended) {
-                    changedPackagesList.add(packageName);
-                    changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
-                }
-
-                modifiedPackages.add(packageName);
-                modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
             }
-        });
+
+            // If size one, the package will be unsuspended from this call
+            boolean packageUnsuspended =
+                    !suspended && CollectionUtils.size(suspendParamsMap) <= 1;
+            if (suspended || packageUnsuspended) {
+                changedPackagesList.add(packageName);
+                changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+            }
+
+            modifiedPackages.add(packageName);
+            modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+        }
 
         mPm.commitPackageStateMutation(null, mutator -> {
             final int size = modifiedPackages.size();
@@ -218,29 +217,27 @@
      * @return The names of packages which are Unsuspendable.
      */
     @NonNull
-    String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId,
-            int callingUid) {
+    String[] getUnsuspendablePackagesForUser(@NonNull Computer computer,
+            @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) {
         if (!isSuspendAllowedForUser(userId, callingUid)) {
             Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
             return packageNames;
         }
         final ArraySet<String> unactionablePackages = new ArraySet<>();
-        mPm.executeWithConsistentComputer(computer -> {
-            final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId,
-                    callingUid);
-            for (int i = 0; i < packageNames.length; i++) {
-                if (!canSuspend[i]) {
-                    unactionablePackages.add(packageNames[i]);
-                    continue;
-                }
-                final PackageStateInternal packageState =
-                        computer.getPackageStateFiltered(packageNames[i], callingUid, userId);
-                if (packageState == null) {
-                    Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
-                    unactionablePackages.add(packageNames[i]);
-                }
+        final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId,
+                callingUid);
+        for (int i = 0; i < packageNames.length; i++) {
+            if (!canSuspend[i]) {
+                unactionablePackages.add(packageNames[i]);
+                continue;
             }
-        });
+            final PackageStateInternal packageState =
+                    computer.getPackageStateFiltered(packageNames[i], callingUid, userId);
+            if (packageState == null) {
+                Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
+                unactionablePackages.add(packageNames[i]);
+            }
+        }
         return unactionablePackages.toArray(new String[unactionablePackages.size()]);
     }
 
@@ -282,45 +279,44 @@
      *                                   suspensions will be removed.
      * @param userId The user for which the changes are taking place.
      */
-    void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange,
+    void removeSuspensionsBySuspendingPackage(@NonNull Computer computer,
+            @NonNull String[] packagesToChange,
             @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
         final List<String> unsuspendedPackages = new ArrayList<>();
         final IntArray unsuspendedUids = new IntArray();
         final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
-        mPm.executeWithConsistentComputer(computer -> {
-            for (String packageName : packagesToChange) {
-                final PackageStateInternal packageState =
-                        computer.getPackageStateInternal(packageName);
-                final PackageUserStateInternal packageUserState = packageState == null
-                        ? null : packageState.getUserStateOrDefault(userId);
-                if (packageUserState == null || !packageUserState.isSuspended()) {
-                    continue;
-                }
+        for (String packageName : packagesToChange) {
+            final PackageStateInternal packageState =
+                    computer.getPackageStateInternal(packageName);
+            final PackageUserStateInternal packageUserState = packageState == null
+                    ? null : packageState.getUserStateOrDefault(userId);
+            if (packageUserState == null || !packageUserState.isSuspended()) {
+                continue;
+            }
 
-                WatchedArrayMap<String, SuspendParams> suspendParamsMap =
-                        packageUserState.getSuspendParams();
-                int countRemoved = 0;
-                for (int index = 0; index < suspendParamsMap.size(); index++) {
-                    String suspendingPackage = suspendParamsMap.keyAt(index);
-                    if (suspendingPackagePredicate.test(suspendingPackage)) {
-                        ArraySet<String> suspendingPkgsToCommit =
-                                pkgToSuspendingPkgsToCommit.get(packageName);
-                        if (suspendingPkgsToCommit == null) {
-                            suspendingPkgsToCommit = new ArraySet<>();
-                            pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit);
-                        }
-                        suspendingPkgsToCommit.add(suspendingPackage);
-                        countRemoved++;
+            WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+                    packageUserState.getSuspendParams();
+            int countRemoved = 0;
+            for (int index = 0; index < suspendParamsMap.size(); index++) {
+                String suspendingPackage = suspendParamsMap.keyAt(index);
+                if (suspendingPackagePredicate.test(suspendingPackage)) {
+                    ArraySet<String> suspendingPkgsToCommit =
+                            pkgToSuspendingPkgsToCommit.get(packageName);
+                    if (suspendingPkgsToCommit == null) {
+                        suspendingPkgsToCommit = new ArraySet<>();
+                        pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit);
                     }
-                }
-
-                // Everything would be removed and package unsuspended
-                if (countRemoved == suspendParamsMap.size()) {
-                    unsuspendedPackages.add(packageState.getPackageName());
-                    unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                    suspendingPkgsToCommit.add(suspendingPackage);
+                    countRemoved++;
                 }
             }
-        });
+
+            // Everything would be removed and package unsuspended
+            if (countRemoved == suspendParamsMap.size()) {
+                unsuspendedPackages.add(packageState.getPackageName());
+                unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+            }
+        }
 
         mPm.commitPackageStateMutation(null, mutator -> {
             for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index b730ab2..e06b608 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -30,6 +30,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.Computer;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
@@ -38,7 +39,6 @@
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.function.Function;
 
 @SuppressWarnings("PointlessBooleanExpression")
 public class DomainVerificationDebug {
@@ -62,8 +62,7 @@
     }
 
     public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
-            @Nullable @UserIdInt Integer userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction,
+            @Nullable @UserIdInt Integer userId, @NonNull Computer snapshot,
             @NonNull DomainVerificationStateMap<DomainVerificationPkgState> stateMap)
             throws NameNotFoundException {
         ArrayMap<String, Integer> reusedMap = new ArrayMap<>();
@@ -74,7 +73,7 @@
             for (int index = 0; index < size; index++) {
                 DomainVerificationPkgState pkgState = stateMap.valueAt(index);
                 String pkgName = pkgState.getPackageName();
-                PackageStateInternal pkgSetting = pkgSettingFunction.apply(pkgName);
+                PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
                 if (pkgSetting == null || pkgSetting.getPkg() == null) {
                     continue;
                 }
@@ -90,7 +89,7 @@
                 throw DomainVerificationUtils.throwPackageUnavailable(packageName);
             }
 
-            PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
             if (pkgSetting == null || pkgSetting.getPkg() == null) {
                 throw DomainVerificationUtils.throwPackageUnavailable(packageName);
             }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 25147d0..1714086 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -25,7 +25,6 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageSettingsSnapshotProvider;
 import android.content.pm.ResolveInfo;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
@@ -37,7 +36,7 @@
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 
-import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.Computer;
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.Settings;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -227,14 +226,14 @@
      * assumed nothing has changed since the device rebooted.
      * <p>
      * If this is a new install, state will be restored from a previous call to {@link
-     * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a
+     * #restoreSettings(Computer, TypedXmlPullParser)}, or a new one will be generated. In either case, a
      * broadcast will be sent to the domain verification agent so it may re-run any verification
      * logic for the newly associated domains.
      * <p>
      * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
      * lock. This should never be called from within the domain verification classes themselves.
      * <p>
-     * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
+     * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
      * handled by the caller.
      */
     void addPackage(@NonNull PackageStateInternal newPkgSetting);
@@ -249,7 +248,7 @@
      * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
      * lock. This should never be called from within the domain verification classes themselves.
      * <p>
-     * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be
+     * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
      * handled by the caller.
      */
     void migrateState(@NonNull PackageStateInternal oldPkgSetting,
@@ -259,24 +258,24 @@
      * Serializes the entire internal state. This is equivalent to a full backup of the existing
      * verification state. This write includes legacy state, as a sibling tag the modern state.
      *
+     * @param snapshot
      * @param includeSignatures Whether to include the package signatures in the output, mainly
      *                          used for backing up the user settings and ensuring they're
      *                          re-attached to the same package.
      * @param userId The user to write out. Supports {@link UserHandle#USER_ALL} if all users
-     *               should be written.
      */
-    void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
-            @UserIdInt int userId) throws IOException;
+    void writeSettings(@NonNull Computer snapshot, @NonNull TypedXmlSerializer serializer,
+            boolean includeSignatures, @UserIdInt int userId) throws IOException;
 
     /**
      * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
-     * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
+     * #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. Assumes that the
      * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered.
      * <p>
      * This is expected to only be used to re-attach states for packages already known to be on the
-     * device. If restoring from a backup, use {@link #restoreSettings(TypedXmlPullParser)}.
+     * device. If restoring from a backup, use {@link #restoreSettings(Computer, TypedXmlPullParser)}.
      */
-    void readSettings(@NonNull TypedXmlPullParser parser)
+    void readSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser)
             throws IOException, XmlPullParserException;
 
     /**
@@ -306,7 +305,7 @@
 
     /**
      * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
-     * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the
+     * #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. Assumes that the
      * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
      * tag has already been entered.
      * <p>
@@ -321,7 +320,7 @@
      * TODO(b/170746586): Figure out how to verify that package signatures match at snapshot time
      *  and restore time.
      */
-    void restoreSettings(@NonNull TypedXmlPullParser parser)
+    void restoreSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser)
             throws IOException, XmlPullParserException;
 
     /**
@@ -349,17 +348,14 @@
     /**
      * Print the verification state and user selection state of a package.
      *
+     * @param snapshot
      * @param packageName        the package whose state to change, or all packages if none is
      *                           specified
      * @param userId             the specific user to print, or null to skip printing user selection
-     *                           states, supports {@link android.os.UserHandle#USER_ALL}
-     * @param pkgSettingFunction the method by which to retrieve package data; if this is called
-     *                           from {@link PackageManagerService}, it is expected to pass in the
-     *                           snapshot of {@link PackageStateInternal} objects
+ *                           states, supports {@link UserHandle#USER_ALL}
      */
-    void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
-            @Nullable @UserIdInt Integer userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
+    void printState(@NonNull Computer snapshot, @NonNull IndentingPrintWriter writer,
+            @Nullable String packageName, @Nullable @UserIdInt Integer userId)
             throws NameNotFoundException;
 
     @NonNull
@@ -406,12 +402,11 @@
             @NonNull Set<String> domains, int state) throws NameNotFoundException;
 
 
-    interface Connection extends DomainVerificationEnforcer.Callback,
-            PackageSettingsSnapshotProvider {
+    interface Connection extends DomainVerificationEnforcer.Callback {
 
         /**
          * Notify that a settings change has been made and that eventually
-         * {@link #writeSettings(TypedXmlSerializer, boolean, int)} should be invoked by the parent.
+         * {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)} should be invoked by the parent.
          */
         void scheduleWriteSettings();
 
@@ -433,5 +428,8 @@
 
         @UserIdInt
         int[] getAllUserIds();
+
+        @NonNull
+        Computer snapshot();
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index d6c89f7..13218ea 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -37,7 +37,6 @@
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.content.pm.verify.domain.DomainVerificationUserState;
 import android.content.pm.verify.domain.IDomainVerificationManager;
-import android.os.Build;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -52,11 +51,10 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.FunctionalUtils;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.compat.PlatformCompat;
-import com.android.server.pm.Settings;
+import com.android.server.pm.Computer;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
@@ -80,7 +78,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
 @SuppressLint("MissingPermission")
@@ -109,9 +106,9 @@
      * immediately attached once its available.
      * <p>
      * Generally this should be not accessed directly. Prefer calling {@link
-     * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function)}.
+     * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Computer)}.
      *
-     * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function)
+     * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Computer)
      **/
     @GuardedBy("mLock")
     @NonNull
@@ -178,12 +175,7 @@
 
     @Override
     public void setConnection(@NonNull Connection connection) {
-        if (Build.IS_USERDEBUG || Build.IS_ENG) {
-            mConnection = new LockSafeConnection(connection);
-        } else {
-            mConnection = connection;
-        }
-
+        mConnection = connection;
         mEnforcer.setCallback(mConnection);
     }
 
@@ -264,44 +256,43 @@
     public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
             throws NameNotFoundException {
         mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
-        return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                PackageStateInternal pkgSetting = pkgSettings.apply(packageName);
-                AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
-                if (pkg == null) {
-                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
-                }
-
-                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
-                if (pkgState == null) {
-                    return null;
-                }
-
-                ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
-
-                // TODO(b/159952358): Should the domain list be cached?
-                ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
-                if (domains.isEmpty()) {
-                    return null;
-                }
-
-                int size = domains.size();
-                for (int index = 0; index < size; index++) {
-                    hostToStateMap.putIfAbsent(domains.valueAt(index),
-                            DomainVerificationState.STATE_NO_RESPONSE);
-                }
-
-                final int mapSize = hostToStateMap.size();
-                for (int index = 0; index < mapSize; index++) {
-                    int internalValue = hostToStateMap.valueAt(index);
-                    int publicValue = DomainVerificationState.convertToInfoState(internalValue);
-                    hostToStateMap.setValueAt(index, publicValue);
-                }
-
-                // TODO(b/159952358): Do not return if no values are editable (all ignored states)?
-                return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
             }
-        });
+
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                return null;
+            }
+
+            ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
+
+            // TODO(b/159952358): Should the domain list be cached?
+            ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
+            if (domains.isEmpty()) {
+                return null;
+            }
+
+            int size = domains.size();
+            for (int index = 0; index < size; index++) {
+                hostToStateMap.putIfAbsent(domains.valueAt(index),
+                        DomainVerificationState.STATE_NO_RESPONSE);
+            }
+
+            final int mapSize = hostToStateMap.size();
+            for (int index = 0; index < mapSize; index++) {
+                int internalValue = hostToStateMap.valueAt(index);
+                int publicValue = DomainVerificationState.convertToInfoState(internalValue);
+                hostToStateMap.setValueAt(index, publicValue);
+            }
+
+            // TODO(b/159952358): Do not return if no values are editable (all ignored states)?
+            return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
+        }
     }
 
     @DomainVerificationManager.Error
@@ -324,42 +315,40 @@
             @NonNull Set<String> domains, int state)
             throws NameNotFoundException {
         mEnforcer.assertApprovedVerifier(callingUid, mProxy);
-        return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                List<String> verifiedDomains = new ArrayList<>();
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            List<String> verifiedDomains = new ArrayList<>();
 
-                GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
-                        true /* forAutoVerify */, callingUid, null /* userId */,
-                        pkgSettings);
-                if (result.isError()) {
-                    return result.getErrorCode();
-                }
-
-                DomainVerificationPkgState pkgState = result.getPkgState();
-                ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-                for (String domain : domains) {
-                    Integer previousState = stateMap.get(domain);
-                    if (previousState != null
-                            && !DomainVerificationState.isModifiable(previousState)) {
-                        continue;
-                    }
-
-                    if (DomainVerificationState.isVerified(state)) {
-                        verifiedDomains.add(domain);
-                    }
-
-                    stateMap.put(domain, state);
-                }
-
-                int size = verifiedDomains.size();
-                for (int index = 0; index < size; index++) {
-                    removeUserStatesForDomain(verifiedDomains.get(index));
-                }
+            GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
+                    true /* forAutoVerify */, callingUid, null /* userId */, snapshot);
+            if (result.isError()) {
+                return result.getErrorCode();
             }
 
-            mConnection.scheduleWriteSettings();
-            return DomainVerificationManager.STATUS_OK;
-        });
+            DomainVerificationPkgState pkgState = result.getPkgState();
+            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+            for (String domain : domains) {
+                Integer previousState = stateMap.get(domain);
+                if (previousState != null
+                        && !DomainVerificationState.isModifiable(previousState)) {
+                    continue;
+                }
+
+                if (DomainVerificationState.isVerified(state)) {
+                    verifiedDomains.add(domain);
+                }
+
+                stateMap.put(domain, state);
+            }
+
+            int size = verifiedDomains.size();
+            for (int index = 0; index < size; index++) {
+                removeUserStatesForDomain(verifiedDomains.get(index));
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+        return DomainVerificationManager.STATUS_OK;
     }
 
     @Override
@@ -380,60 +369,30 @@
 
         ArraySet<String> verifiedDomains = new ArraySet<>();
         if (packageName == null) {
-            mConnection.withPackageSettingsSnapshot(pkgSettings -> {
-                synchronized (mLock) {
-                    ArraySet<String> validDomains = new ArraySet<>();
+            synchronized (mLock) {
+                final Computer snapshot = mConnection.snapshot();
+                ArraySet<String> validDomains = new ArraySet<>();
 
-                    int size = mAttachedPkgStates.size();
-                    for (int index = 0; index < size; index++) {
-                        DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
-                        String pkgName = pkgState.getPackageName();
-                        PackageStateInternal pkgSetting = pkgSettings.apply(pkgName);
-                        if (pkgSetting == null || pkgSetting.getPkg() == null) {
-                            continue;
-                        }
-
-                        AndroidPackage pkg = pkgSetting.getPkg();
-
-                        validDomains.clear();
-
-                        ArraySet<String> autoVerifyDomains =
-                                mCollector.collectValidAutoVerifyDomains(pkg);
-                        if (domains == null) {
-                            validDomains.addAll(autoVerifyDomains);
-                        } else {
-                            validDomains.addAll(domains);
-                            validDomains.retainAll(autoVerifyDomains);
-                        }
-
-                        if (DomainVerificationState.isVerified(state)) {
-                            verifiedDomains.addAll(validDomains);
-                        }
-
-                        setDomainVerificationStatusInternal(pkgState, state, validDomains);
-                    }
-                }
-            });
-        } else {
-            mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> {
-                synchronized (mLock) {
-                    DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
-                    if (pkgState == null) {
-                        throw DomainVerificationUtils.throwPackageUnavailable(packageName);
-                    }
-
-                    PackageStateInternal pkgSetting = pkgSettings.apply(packageName);
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    String pkgName = pkgState.getPackageName();
+                    PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
                     if (pkgSetting == null || pkgSetting.getPkg() == null) {
-                        throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                        continue;
                     }
 
                     AndroidPackage pkg = pkgSetting.getPkg();
-                    final ArraySet<String> validDomains;
+
+                    validDomains.clear();
+
+                    ArraySet<String> autoVerifyDomains =
+                            mCollector.collectValidAutoVerifyDomains(pkg);
                     if (domains == null) {
-                        validDomains = mCollector.collectValidAutoVerifyDomains(pkg);
+                        validDomains.addAll(autoVerifyDomains);
                     } else {
-                        validDomains = domains;
-                        validDomains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg));
+                        validDomains.addAll(domains);
+                        validDomains.retainAll(autoVerifyDomains);
                     }
 
                     if (DomainVerificationState.isVerified(state)) {
@@ -442,7 +401,35 @@
 
                     setDomainVerificationStatusInternal(pkgState, state, validDomains);
                 }
-            });
+            }
+        } else {
+            synchronized (mLock) {
+                final Computer snapshot = mConnection.snapshot();
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+                if (pkgState == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
+                if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                AndroidPackage pkg = pkgSetting.getPkg();
+                final ArraySet<String> validDomains;
+                if (domains == null) {
+                    validDomains = mCollector.collectValidAutoVerifyDomains(pkg);
+                } else {
+                    validDomains = domains;
+                    validDomains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg));
+                }
+
+                if (DomainVerificationState.isVerified(state)) {
+                    verifiedDomains.addAll(validDomains);
+                }
+
+                setDomainVerificationStatusInternal(pkgState, state, validDomains);
+            }
         }
 
         // Mirror SystemApi behavior of revoking user selection for approved domains.
@@ -552,39 +539,38 @@
             return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID;
         }
 
-        return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
-                        false /* forAutoVerify */, callingUid, userId, pkgSettings);
-                if (result.isError()) {
-                    return result.getErrorCode();
-                }
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
+                    false /* forAutoVerify */, callingUid, userId, snapshot);
+            if (result.isError()) {
+                return result.getErrorCode();
+            }
 
-                DomainVerificationPkgState pkgState = result.getPkgState();
-                DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(
-                        userId);
+            DomainVerificationPkgState pkgState = result.getPkgState();
+            DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(
+                    userId);
 
-                // Disable other packages if approving this one. Note that this check is only done
-                // for enabling. This allows an escape hatch in case multiple packages somehow get
-                // selected. They can be disabled without blocking in a circular dependency.
-                if (enabled) {
-                    int statusCode = revokeOtherUserSelectionsLocked(userState, userId, domains,
-                            pkgSettings);
-                    if (statusCode != DomainVerificationManager.STATUS_OK) {
-                        return statusCode;
-                    }
-                }
-
-                if (enabled) {
-                    userState.addHosts(domains);
-                } else {
-                    userState.removeHosts(domains);
+            // Disable other packages if approving this one. Note that this check is only done
+            // for enabling. This allows an escape hatch in case multiple packages somehow get
+            // selected. They can be disabled without blocking in a circular dependency.
+            if (enabled) {
+                int statusCode = revokeOtherUserSelectionsLocked(userState, userId, domains,
+                        snapshot);
+                if (statusCode != DomainVerificationManager.STATUS_OK) {
+                    return statusCode;
                 }
             }
 
-            mConnection.scheduleWriteSettings();
-            return DomainVerificationManager.STATUS_OK;
-        });
+            if (enabled) {
+                userState.addHosts(domains);
+            } else {
+                userState.removeHosts(domains);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+        return DomainVerificationManager.STATUS_OK;
     }
 
     @Override
@@ -592,48 +578,47 @@
             @NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains)
             throws NameNotFoundException {
         mEnforcer.assertInternal(mConnection.getCallingUid());
-        mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
-                if (pkgState == null) {
-                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
-                }
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
 
-                PackageStateInternal pkgSetting = pkgSettings.apply(packageName);
-                AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
-                if (pkg == null) {
-                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
-                }
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
 
-                Set<String> validDomains =
-                        domains == null ? mCollector.collectAllWebDomains(pkg) : domains;
+            Set<String> validDomains =
+                    domains == null ? mCollector.collectAllWebDomains(pkg) : domains;
 
-                validDomains.retainAll(mCollector.collectAllWebDomains(pkg));
+            validDomains.retainAll(mCollector.collectAllWebDomains(pkg));
 
-                if (userId == UserHandle.USER_ALL) {
-                    for (int aUserId : mConnection.getAllUserIds()) {
-                        DomainVerificationInternalUserState userState =
-                                pkgState.getOrCreateUserState(aUserId);
-                        revokeOtherUserSelectionsLocked(userState, aUserId, validDomains,
-                                pkgSettings);
-                        if (enabled) {
-                            userState.addHosts(validDomains);
-                        } else {
-                            userState.removeHosts(validDomains);
-                        }
-                    }
-                } else {
+            if (userId == UserHandle.USER_ALL) {
+                for (int aUserId : mConnection.getAllUserIds()) {
                     DomainVerificationInternalUserState userState =
-                            pkgState.getOrCreateUserState(userId);
-                    revokeOtherUserSelectionsLocked(userState, userId, validDomains, pkgSettings);
+                            pkgState.getOrCreateUserState(aUserId);
+                    revokeOtherUserSelectionsLocked(userState, aUserId, validDomains,
+                            snapshot);
                     if (enabled) {
                         userState.addHosts(validDomains);
                     } else {
                         userState.removeHosts(validDomains);
                     }
                 }
+            } else {
+                DomainVerificationInternalUserState userState =
+                        pkgState.getOrCreateUserState(userId);
+                revokeOtherUserSelectionsLocked(userState, userId, validDomains, snapshot);
+                if (enabled) {
+                    userState.addHosts(validDomains);
+                } else {
+                    userState.removeHosts(validDomains);
+                }
             }
-        });
+        }
 
         mConnection.scheduleWriteSettings();
     }
@@ -641,8 +626,7 @@
     @GuardedBy("mLock")
     private int revokeOtherUserSelectionsLocked(
             @NonNull DomainVerificationInternalUserState userState, @UserIdInt int userId,
-            @NonNull Set<String> domains,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
+            @NonNull Set<String> domains, @NonNull Computer snapshot) {
         // Cache the approved packages from the 1st pass because the search is expensive
         ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>();
 
@@ -652,7 +636,7 @@
             }
 
             Pair<List<String>, Integer> packagesToLevel = getApprovedPackagesLocked(domain,
-                    userId, APPROVAL_LEVEL_NONE + 1, pkgSettingFunction);
+                    userId, APPROVAL_LEVEL_NONE + 1, snapshot);
             int highestApproval = packagesToLevel.second;
             if (highestApproval > APPROVAL_LEVEL_SELECTION) {
                 return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE;
@@ -698,51 +682,50 @@
             throw DomainVerificationUtils.throwPackageUnavailable(packageName);
         }
 
-        return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                PackageStateInternal pkgSetting = pkgSettings.apply(packageName);
-                AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
-                if (pkg == null) {
-                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
-                }
-
-                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
-                if (pkgState == null) {
-                    return null;
-                }
-
-                ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
-                int webDomainsSize = webDomains.size();
-
-                Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
-                ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-                DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
-                Set<String> enabledHosts =
-                        userState == null ? emptySet() : userState.getEnabledHosts();
-
-                for (int index = 0; index < webDomainsSize; index++) {
-                    String host = webDomains.valueAt(index);
-                    Integer state = stateMap.get(host);
-
-                    int domainState;
-                    if (state != null && DomainVerificationState.isVerified(state)) {
-                        domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
-                    } else if (enabledHosts.contains(host)) {
-                        domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
-                    } else {
-                        domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
-                    }
-
-                    domains.put(host, domainState);
-                }
-
-                boolean linkHandlingAllowed =
-                        userState == null || userState.isLinkHandlingAllowed();
-
-                return new DomainVerificationUserState(pkgState.getId(), packageName,
-                        UserHandle.of(userId), linkHandlingAllowed, domains);
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
             }
-        });
+
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                return null;
+            }
+
+            ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
+            int webDomainsSize = webDomains.size();
+
+            Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
+            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+            DomainVerificationInternalUserState userState = pkgState.getUserState(userId);
+            Set<String> enabledHosts =
+                    userState == null ? emptySet() : userState.getEnabledHosts();
+
+            for (int index = 0; index < webDomainsSize; index++) {
+                String host = webDomains.valueAt(index);
+                Integer state = stateMap.get(host);
+
+                int domainState;
+                if (state != null && DomainVerificationState.isVerified(state)) {
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
+                } else if (enabledHosts.contains(host)) {
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
+                } else {
+                    domainState = DomainVerificationUserState.DOMAIN_STATE_NONE;
+                }
+
+                domains.put(host, domainState);
+            }
+
+            boolean linkHandlingAllowed =
+                    userState == null || userState.isLinkHandlingAllowed();
+
+            return new DomainVerificationUserState(pkgState.getId(), packageName,
+                    UserHandle.of(userId), linkHandlingAllowed, domains);
+        }
     }
 
     @NonNull
@@ -751,27 +734,26 @@
         mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
                 userId);
 
-        return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> {
-            SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false,
-                    userId, pkgSettings);
-            if (levelToPackages.size() == 0) {
-                return emptyList();
-            }
+        final Computer snapshot = mConnection.snapshot();
+        SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false,
+                userId, snapshot);
+        if (levelToPackages.size() == 0) {
+            return emptyList();
+        }
 
-            List<DomainOwner> owners = new ArrayList<>();
-            int size = levelToPackages.size();
-            for (int index = 0; index < size; index++) {
-                int level = levelToPackages.keyAt(index);
-                boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
-                List<String> packages = levelToPackages.valueAt(index);
-                int packagesSize = packages.size();
-                for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
-                    owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
-                }
+        List<DomainOwner> owners = new ArrayList<>();
+        int size = levelToPackages.size();
+        for (int index = 0; index < size; index++) {
+            int level = levelToPackages.keyAt(index);
+            boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
+            List<String> packages = levelToPackages.valueAt(index);
+            int packagesSize = packages.size();
+            for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
+                owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
             }
+        }
 
-            return owners;
-        });
+        return owners;
     }
 
     /**
@@ -782,8 +764,7 @@
      */
     @NonNull
     private SparseArray<List<String>> getOwnersForDomainInternal(@NonNull String domain,
-            boolean includeNegative, @UserIdInt int userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
+            boolean includeNegative, @UserIdInt int userId, @NonNull Computer snapshot) {
         SparseArray<List<String>> levelToPackages = new SparseArray<>();
         // First, collect the raw approval level values
         synchronized (mLock) {
@@ -791,7 +772,7 @@
             for (int index = 0; index < size; index++) {
                 DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
                 String packageName = pkgState.getPackageName();
-                PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
+                PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
                 if (pkgSetting == null) {
                     continue;
                 }
@@ -818,8 +799,8 @@
         // Then sort them ascending by first installed time, with package name as tie breaker
         for (int index = 0; index < size; index++) {
             levelToPackages.valueAt(index).sort((first, second) -> {
-                PackageStateInternal firstPkgSetting = pkgSettingFunction.apply(first);
-                PackageStateInternal secondPkgSetting = pkgSettingFunction.apply(second);
+                PackageStateInternal firstPkgSetting = snapshot.getPackageStateInternal(first);
+                PackageStateInternal secondPkgSetting = snapshot.getPackageStateInternal(second);
 
                 long firstInstallTime = firstPkgSetting == null
                         ? -1L : firstPkgSetting.getUserStateOrDefault(userId).getFirstInstallTime();
@@ -1060,44 +1041,38 @@
     }
 
     @Override
-    public void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures,
-            @UserIdInt int userId)
-            throws IOException {
-        mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                Function<String, String> pkgNameToSignature = null;
-                if (includeSignatures) {
-                    pkgNameToSignature = pkgName -> {
-                        PackageStateInternal pkgSetting = pkgSettings.apply(pkgName);
-                        if (pkgSetting == null) {
-                            // If querying for a user restored package that isn't installed on the
-                            // device yet, there will be no signature to write out. In that case,
-                            // it's expected that this returns null and it falls back to the
-                            // restored state's stored signature if it exists.
-                            return null;
-                        }
+    public void writeSettings(Computer snapshot, @NonNull TypedXmlSerializer serializer,
+            boolean includeSignatures, @UserIdInt int userId) throws IOException {
+        synchronized (mLock) {
+            Function<String, String> pkgNameToSignature = null;
+            if (includeSignatures) {
+                pkgNameToSignature = pkgName -> {
+                    PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
+                    if (pkgSetting == null) {
+                        // If querying for a user restored package that isn't installed on the
+                        // device yet, there will be no signature to write out. In that case,
+                        // it's expected that this returns null and it falls back to the
+                        // restored state's stored signature if it exists.
+                        return null;
+                    }
 
-                        return PackageUtils.computeSignaturesSha256Digest(
-                                pkgSetting.getSigningDetails().getSignatures());
-                    };
-                }
-
-                mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature);
+                    return PackageUtils.computeSignaturesSha256Digest(
+                            pkgSetting.getSigningDetails().getSignatures());
+                };
             }
-        });
+
+            mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature);
+        }
 
         mLegacySettings.writeSettings(serializer);
     }
 
     @Override
-    public void readSettings(@NonNull TypedXmlPullParser parser) throws IOException,
-            XmlPullParserException {
-        mConnection.<IOException, XmlPullParserException>withPackageSettingsSnapshotThrowing2(
-                pkgSettings -> {
-                    synchronized (mLock) {
-                        mSettings.readSettings(parser, mAttachedPkgStates, pkgSettings);
-                    }
-                });
+    public void readSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            mSettings.readSettings(parser, mAttachedPkgStates, snapshot);
+        }
     }
 
     @Override
@@ -1107,14 +1082,11 @@
     }
 
     @Override
-    public void restoreSettings(@NonNull TypedXmlPullParser parser)
+    public void restoreSettings(Computer snapshot, @NonNull TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-        mConnection.<IOException, XmlPullParserException>withPackageSettingsSnapshotThrowing2(
-                pkgSettings -> {
-                    synchronized (mLock) {
-                        mSettings.restoreSettings(parser, mAttachedPkgStates, pkgSettings);
-                    }
-                });
+        synchronized (mLock) {
+            mSettings.restoreSettings(parser, mAttachedPkgStates, snapshot);
+        }
     }
 
     @Override
@@ -1190,18 +1162,16 @@
     @Override
     public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
             @Nullable Integer userId) throws NameNotFoundException {
-        mConnection.withPackageSettingsSnapshotThrowing(
-                pkgSettings -> printState(writer, packageName, userId, pkgSettings));
+        printState(mConnection.snapshot(), writer, packageName, userId);
     }
 
     @Override
-    public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
-            @Nullable @UserIdInt Integer userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
+    public void printState(@NonNull Computer snapshot, @NonNull IndentingPrintWriter writer,
+            @Nullable String packageName, @Nullable @UserIdInt Integer userId)
             throws NameNotFoundException {
         mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
         synchronized (mLock) {
-            mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
+            mDebug.printState(writer, packageName, userId, snapshot, mAttachedPkgStates);
         }
     }
 
@@ -1209,31 +1179,30 @@
     public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
             @Nullable String packageName, @Nullable @UserIdInt Integer userId)
             throws NameNotFoundException {
-        mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> {
-            synchronized (mLock) {
-                if (packageName == null) {
-                    int size = mAttachedPkgStates.size();
-                    for (int index = 0; index < size; index++) {
-                        try {
-                            printOwnersForPackage(writer,
-                                    mAttachedPkgStates.valueAt(index).getPackageName(), userId,
-                                    pkgSettings);
-                        } catch (NameNotFoundException ignored) {
-                            // When iterating packages, if one doesn't exist somehow, ignore
-                        }
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            if (packageName == null) {
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    try {
+                        printOwnersForPackage(writer,
+                                mAttachedPkgStates.valueAt(index).getPackageName(), userId,
+                                snapshot);
+                    } catch (NameNotFoundException ignored) {
+                        // When iterating packages, if one doesn't exist somehow, ignore
                     }
-                } else {
-                    printOwnersForPackage(writer, packageName, userId, pkgSettings);
                 }
+            } else {
+                printOwnersForPackage(writer, packageName, userId, snapshot);
             }
-        });
+        }
     }
 
     private void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
             @NonNull String packageName, @Nullable @UserIdInt Integer userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
+            @NonNull Computer snapshot)
             throws NameNotFoundException {
-        PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
+        PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
         AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
         if (pkg == null) {
             throw DomainVerificationUtils.throwPackageUnavailable(packageName);
@@ -1249,7 +1218,7 @@
         writer.increaseIndent();
 
         for (int index = 0; index < size; index++) {
-            printOwnersForDomain(writer, domains.valueAt(index), userId, pkgSettingFunction);
+            printOwnersForDomain(writer, domains.valueAt(index), userId, snapshot);
         }
 
         writer.decreaseIndent();
@@ -1258,30 +1227,28 @@
     @Override
     public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
             @NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
-        mConnection.withPackageSettingsSnapshot(pkgSettings -> {
-            synchronized (mLock) {
-                int size = domains.size();
-                for (int index = 0; index < size; index++) {
-                    printOwnersForDomain(writer, domains.get(index), userId, pkgSettings);
-                }
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            int size = domains.size();
+            for (int index = 0; index < size; index++) {
+                printOwnersForDomain(writer, domains.get(index), userId, snapshot);
             }
-        });
+        }
     }
 
     private void printOwnersForDomain(@NonNull IndentingPrintWriter writer, @NonNull String domain,
-            @Nullable @UserIdInt Integer userId,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
+            @Nullable @UserIdInt Integer userId, @NonNull Computer snapshot) {
         SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners =
                 new SparseArray<>();
 
         if (userId == null || userId == UserHandle.USER_ALL) {
             for (int aUserId : mConnection.getAllUserIds()) {
                 userIdToApprovalLevelToOwners.put(aUserId,
-                        getOwnersForDomainInternal(domain, true, aUserId, pkgSettingFunction));
+                        getOwnersForDomainInternal(domain, true, aUserId, snapshot));
             }
         } else {
             userIdToApprovalLevelToOwners.put(userId,
-                    getOwnersForDomainInternal(domain, true, userId, pkgSettingFunction));
+                    getOwnersForDomainInternal(domain, true, userId, snapshot));
         }
 
         mDebug.printOwners(writer, domain, userIdToApprovalLevelToOwners);
@@ -1330,8 +1297,7 @@
     @GuardedBy("mLock")
     private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId,
             @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
-            @Nullable Integer userIdForFilter,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
+            @Nullable Integer userIdForFilter, @NonNull Computer snapshot)
             throws NameNotFoundException {
         if (domainSetId == null) {
             throw new IllegalArgumentException("domainSetId cannot be null");
@@ -1349,7 +1315,7 @@
             return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
         }
 
-        PackageStateInternal pkgSetting = pkgSettingFunction.apply(pkgName);
+        PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
         if (pkgSetting == null || pkgSetting.getPkg() == null) {
             throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
         }
@@ -1437,33 +1403,32 @@
     @Override
     public void clearDomainVerificationState(@Nullable List<String> packageNames) {
         mEnforcer.assertInternal(mConnection.getCallingUid());
-        mConnection.withPackageSettingsSnapshot(pkgSettings -> {
-            synchronized (mLock) {
-                if (packageNames == null) {
-                    int size = mAttachedPkgStates.size();
-                    for (int index = 0; index < size; index++) {
-                        DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
-                        String pkgName = pkgState.getPackageName();
-                        PackageStateInternal pkgSetting = pkgSettings.apply(pkgName);
-                        if (pkgSetting == null || pkgSetting.getPkg() == null) {
-                            continue;
-                        }
-                        resetDomainState(pkgState.getStateMap(), pkgSetting);
+        synchronized (mLock) {
+            final Computer snapshot = mConnection.snapshot();
+            if (packageNames == null) {
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    String pkgName = pkgState.getPackageName();
+                    PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
                     }
-                } else {
-                    int size = packageNames.size();
-                    for (int index = 0; index < size; index++) {
-                        String pkgName = packageNames.get(index);
-                        DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
-                        PackageStateInternal pkgSetting = pkgSettings.apply(pkgName);
-                        if (pkgSetting == null || pkgSetting.getPkg() == null) {
-                            continue;
-                        }
-                        resetDomainState(pkgState.getStateMap(), pkgSetting);
+                    resetDomainState(pkgState.getStateMap(), pkgSetting);
+                }
+            } else {
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String pkgName = packageNames.get(index);
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+                    PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
                     }
+                    resetDomainState(pkgState.getStateMap(), pkgSetting);
                 }
             }
-        });
+        }
 
         mConnection.scheduleWriteSettings();
     }
@@ -1935,35 +1900,32 @@
     @GuardedBy("mLock")
     @NonNull
     private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain,
-            @UserIdInt int userId, int minimumApproval,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
+            @UserIdInt int userId, int minimumApproval, @NonNull Computer snapshot) {
         boolean includeNegative = minimumApproval < APPROVAL_LEVEL_NONE;
         int highestApproval = minimumApproval;
         List<String> approvedPackages = emptyList();
 
-        synchronized (mLock) {
-            final int size = mAttachedPkgStates.size();
-            for (int index = 0; index < size; index++) {
-                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
-                String packageName = pkgState.getPackageName();
-                PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
-                if (pkgSetting == null) {
-                    continue;
-                }
+        final int size = mAttachedPkgStates.size();
+        for (int index = 0; index < size; index++) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+            String packageName = pkgState.getPackageName();
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
+            if (pkgSetting == null) {
+                continue;
+            }
 
-                int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
-                        domain);
-                if (level < minimumApproval) {
-                    continue;
-                }
+            int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
+                    domain);
+            if (level < minimumApproval) {
+                continue;
+            }
 
-                if (level > highestApproval) {
-                    approvedPackages.clear();
-                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
-                    highestApproval = level;
-                } else if (level == highestApproval) {
-                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
-                }
+            if (level > highestApproval) {
+                approvedPackages.clear();
+                approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                highestApproval = level;
+            } else if (level == highestApproval) {
+                approvedPackages = CollectionUtils.add(approvedPackages, packageName);
             }
         }
 
@@ -1976,7 +1938,7 @@
         final int approvedSize = approvedPackages.size();
         for (int index = 0; index < approvedSize; index++) {
             String packageName = approvedPackages.get(index);
-            PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName);
+            PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName);
             if (pkgSetting == null) {
                 continue;
             }
@@ -2035,108 +1997,4 @@
             return mErrorCode;
         }
     }
-
-    /**
-     * Wraps a {@link Connection} to verify that the {@link PackageStateInternal} calls do not hold
-     * {@link #mLock}, as that can cause deadlock when {@link Settings} tries to serialize state to
-     * disk. Only enabled if {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} is true.
-     */
-    private class LockSafeConnection implements Connection {
-
-        @NonNull
-        private final Connection mConnection;
-
-        private LockSafeConnection(@NonNull Connection connection) {
-            mConnection = connection;
-        }
-
-        private void enforceLocking() {
-            if (Thread.holdsLock(mLock)) {
-                Slog.wtf(TAG, "Method should not hold DVS lock when accessing package data");
-            }
-        }
-
-        @Override
-        public void withPackageSettingsSnapshot(
-                @NonNull Consumer<Function<String, PackageStateInternal>> block) {
-            enforceLocking();
-            mConnection.withPackageSettingsSnapshot(block);
-        }
-
-        @Override
-        public <Output> Output withPackageSettingsSnapshotReturning(
-                @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>,
-                        Output> block) {
-            enforceLocking();
-            return mConnection.withPackageSettingsSnapshotReturning(block);
-        }
-
-        @Override
-        public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing(
-                @NonNull FunctionalUtils.ThrowingCheckedConsumer<
-                        Function<String, PackageStateInternal>, ExceptionType> block)
-                throws ExceptionType {
-            enforceLocking();
-            mConnection.withPackageSettingsSnapshotThrowing(block);
-        }
-
-        @Override
-        public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void
-                withPackageSettingsSnapshotThrowing2(
-                        @NonNull FunctionalUtils.ThrowingChecked2Consumer<
-                                Function<String, PackageStateInternal>, ExceptionOne,
-                                ExceptionTwo> block)
-                throws ExceptionOne, ExceptionTwo {
-            enforceLocking();
-            mConnection.withPackageSettingsSnapshotThrowing2(block);
-        }
-
-        @Override
-        public <Output, ExceptionType extends Exception> Output
-                withPackageSettingsSnapshotReturningThrowing(
-                        @NonNull FunctionalUtils.ThrowingCheckedFunction<
-                                Function<String, PackageStateInternal>, Output,
-                                ExceptionType> block)
-                throws ExceptionType {
-            enforceLocking();
-            return mConnection.withPackageSettingsSnapshotReturningThrowing(block);
-        }
-
-        @Override
-        public void scheduleWriteSettings() {
-            mConnection.scheduleWriteSettings();
-        }
-
-        @Override
-        public int getCallingUid() {
-            return mConnection.getCallingUid();
-        }
-
-        @Override
-        @UserIdInt
-        public int getCallingUserId() {
-            return mConnection.getCallingUserId();
-        }
-
-        @Override
-        public void schedule(int code, @Nullable Object object) {
-            mConnection.schedule(code, object);
-        }
-
-        @Override
-        @UserIdInt
-        public int[] getAllUserIds() {
-            return mConnection.getAllUserIds();
-        }
-
-        @Override
-        public boolean filterAppAccess(@NonNull String packageName, int callingUid, int userId) {
-            return mConnection.filterAppAccess(packageName, callingUid, userId);
-        }
-
-        @Override
-        public boolean doesUserExist(int userId) {
-            return mConnection.doesUserExist(userId);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index 015d4e9..8d1ae0b 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.Computer;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
@@ -101,8 +102,7 @@
      */
     public void readSettings(@NonNull TypedXmlPullParser parser,
             @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
-            throws IOException, XmlPullParserException {
+            @NonNull Computer snapshot) throws IOException, XmlPullParserException {
         DomainVerificationPersistence.ReadResult result =
                 DomainVerificationPersistence.readFromXml(parser);
         ArrayMap<String, DomainVerificationPkgState> active = result.active;
@@ -118,7 +118,7 @@
                     // This branch should never be possible. Settings should be read from disk
                     // before any states are attached. But just in case, handle it.
                     if (!existingState.getId().equals(pkgState.getId())) {
-                        mergePkgState(existingState, pkgState, pkgSettingFunction);
+                        mergePkgState(existingState, pkgState, snapshot);
                     }
                 } else {
                     mPendingPkgStates.put(pkgName, pkgState);
@@ -139,8 +139,7 @@
      */
     public void restoreSettings(@NonNull TypedXmlPullParser parser,
             @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
-            throws IOException, XmlPullParserException {
+            @NonNull Computer snapshot) throws IOException, XmlPullParserException {
         // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
         //  a new device.
 
@@ -166,7 +165,7 @@
                 }
 
                 if (existingState != null) {
-                    mergePkgState(existingState, newState, pkgSettingFunction);
+                    mergePkgState(existingState, newState, snapshot);
                 } else {
                     // If there's no existing state, that means the new state has to be transformed
                     // in preparation for attaching to brand new package that may eventually be
@@ -216,9 +215,9 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void mergePkgState(@NonNull DomainVerificationPkgState oldState,
-            @NonNull DomainVerificationPkgState newState,
-            @NonNull Function<String, PackageStateInternal> pkgSettingFunction) {
-        PackageStateInternal pkgSetting = pkgSettingFunction.apply(oldState.getPackageName());
+            @NonNull DomainVerificationPkgState newState, @NonNull Computer snapshot) {
+        PackageStateInternal pkgSetting =
+                snapshot.getPackageStateInternal(oldState.getPackageName());
         AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
         Set<String> validDomains = pkg == null
                 ? Collections.emptySet() : mCollector.collectValidAutoVerifyDomains(pkg);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index da2d162..4b0a8e2 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -32,6 +32,7 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.server.pm.Computer;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -39,7 +40,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.function.Function;
 
 public class DomainVerificationShell {
 
@@ -599,8 +599,8 @@
         void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
 
         /**
-         * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer,
-         * Function)
+         * @see DomainVerificationManagerInternal#printState(Computer, IndentingPrintWriter, String,
+         * Integer)
          */
         void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
                 @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
new file mode 100644
index 0000000..3550bda
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represent a step on a single vibrator that plays one or more segments from a
+ * {@link VibrationEffect.Composed} effect.
+ */
+abstract class AbstractVibratorStep extends Step {
+    public final VibratorController controller;
+    public final VibrationEffect.Composed effect;
+    public final int segmentIndex;
+    public final long previousStepVibratorOffTimeout;
+
+    long mVibratorOnResult;
+    boolean mVibratorCompleteCallbackReceived;
+
+    /**
+     * @param conductor          The VibrationStepConductor for these steps.
+     * @param startTime          The time to schedule this step in the
+     *                           {@link VibrationStepConductor}.
+     * @param controller         The vibrator that is playing the effect.
+     * @param effect             The effect being played in this step.
+     * @param index              The index of the next segment to be played by this step
+     * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any
+     *                           previous vibration and turn off. This is used to allow this step to
+     *                           be triggered when the completion callback is received, and can
+     *                           be used to play effects back-to-back.
+     */
+    AbstractVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        super(conductor, startTime);
+        this.controller = controller;
+        this.effect = effect;
+        this.segmentIndex = index;
+        this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout;
+    }
+
+    public int getVibratorId() {
+        return controller.getVibratorInfo().getId();
+    }
+
+    @Override
+    public long getVibratorOnDuration() {
+        return mVibratorOnResult;
+    }
+
+    @Override
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
+        mVibratorCompleteCallbackReceived |= isSameVibrator;
+        // Only activate this step if a timeout was set to wait for the vibration to complete,
+        // otherwise we are waiting for the correct time to play the next step.
+        return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis());
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(),
+                /* cancelled= */ true, controller, previousStepVibratorOffTimeout));
+    }
+
+    @Override
+    public void cancelImmediately() {
+        if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) {
+            // Vibrator might be running from previous steps, so turn it off while canceling.
+            stopVibrating();
+        }
+    }
+
+    protected void stopVibrating() {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turning off vibrator " + getVibratorId());
+        }
+        controller.off();
+    }
+
+    protected void changeAmplitude(float amplitude) {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
+        }
+        controller.setAmplitude(amplitude);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping
+     * the segments.
+     */
+    protected List<Step> skipToNextSteps(int segmentsSkipped) {
+        return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings
+     * calculated from {@link #getVibratorOnDuration()}, jumping all played segments.
+     *
+     * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
+     * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
+     */
+    protected List<Step> nextSteps(int segmentsPlayed) {
+        if (mVibratorOnResult <= 0) {
+            // Vibration was not started, so just skip the played segments and keep timings.
+            return skipToNextSteps(segmentsPlayed);
+        }
+        long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
+        long nextVibratorOffTimeout =
+                nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+        return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
+    }
+
+    /**
+     * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings,
+     * which might be calculated independently, jumping all played segments.
+     *
+     * <p>This should be used when the vibrator on/off state is not responsible for the steps
+     * execution timings, e.g. while playing the vibrator amplitudes.
+     */
+    protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
+            int segmentsPlayed) {
+        Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
+                segmentIndex + segmentsPlayed, vibratorOffTimeout);
+        return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
new file mode 100644
index 0000000..8585e34
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to complete a {@link VibrationEffect}.
+ *
+ * <p>This runs right at the time the vibration is considered to end and will update the pending
+ * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
+ */
+final class CompleteEffectVibratorStep extends AbstractVibratorStep {
+    private final boolean mCancelled;
+
+    CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled,
+            VibratorController controller, long previousStepVibratorOffTimeout) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+                previousStepVibratorOffTimeout);
+        mCancelled = cancelled;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
+        // Otherwise this step is part of the vibration.
+        return mCancelled;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        if (mCancelled) {
+            // Double cancelling will just turn off the vibrator right away.
+            return Arrays.asList(
+                    new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+        }
+        return super.cancel();
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
+                                + " step on vibrator " + controller.getVibratorInfo().getId());
+            }
+            if (mVibratorCompleteCallbackReceived) {
+                // Vibration completion callback was received by this step, just turn if off
+                // and skip any clean-up.
+                stopVibrating();
+                return VibrationStepConductor.EMPTY_STEP_LIST;
+            }
+
+            float currentAmplitude = controller.getCurrentAmplitude();
+            long remainingOnDuration =
+                    previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT
+                            - SystemClock.uptimeMillis();
+            long rampDownDuration =
+                    Math.min(remainingOnDuration,
+                            conductor.vibrationSettings.getRampDownDuration());
+            long stepDownDuration = conductor.vibrationSettings.getRampStepDuration();
+            if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN
+                    || rampDownDuration <= stepDownDuration) {
+                // No need to ramp down the amplitude, just wait to turn it off.
+                if (mCancelled) {
+                    // Vibration is completing because it was cancelled, turn off right away.
+                    stopVibrating();
+                    return VibrationStepConductor.EMPTY_STEP_LIST;
+                } else {
+                    return Arrays.asList(new TurnOffVibratorStep(
+                            conductor, previousStepVibratorOffTimeout, controller));
+                }
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Ramping down vibrator " + controller.getVibratorInfo().getId()
+                                + " from amplitude " + currentAmplitude
+                                + " for " + rampDownDuration + "ms");
+            }
+            float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
+            float amplitudeTarget = currentAmplitude - amplitudeDelta;
+            long newVibratorOffTimeout =
+                    mCancelled ? rampDownDuration : previousStepVibratorOffTimeout;
+            return Arrays.asList(
+                    new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta,
+                            controller, newVibratorOffTimeout));
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
new file mode 100644
index 0000000..d1ea805
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of primitives.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link PrimitiveSegment} starting at the current index.
+ */
+final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+
+    ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
+        try {
+            // Load the next PrimitiveSegments to create a single compose call to the vibrator,
+            // limited to the vibrator composition maximum size.
+            int limit = controller.getVibratorInfo().getCompositionSizeMax();
+            int segmentCount = limit > 0
+                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+                    : effect.getSegments().size();
+            List<PrimitiveSegment> primitives = new ArrayList<>();
+            for (int i = segmentIndex; i < segmentCount; i++) {
+                VibrationEffectSegment segment = effect.getSegments().get(i);
+                if (segment instanceof PrimitiveSegment) {
+                    primitives.add((PrimitiveSegment) segment);
+                } else {
+                    break;
+                }
+            }
+
+            if (primitives.isEmpty()) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
+                        + effect.getSegments().get(segmentIndex));
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+            mVibratorOnResult = controller.on(
+                    primitives.toArray(new PrimitiveSegment[primitives.size()]),
+                    getVibration().id);
+
+            return nextSteps(/* segmentsPlayed= */ primitives.size());
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
new file mode 100644
index 0000000..73bf933
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on using a composition of PWLE segments.
+ *
+ * <p>This step will use the maximum supported number of consecutive segments of type
+ * {@link StepSegment} or {@link RampSegment} starting at the current index.
+ */
+final class ComposePwleVibratorStep extends AbstractVibratorStep {
+
+    ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
+        try {
+            // Load the next RampSegments to create a single composePwle call to the vibrator,
+            // limited to the vibrator PWLE maximum size.
+            int limit = controller.getVibratorInfo().getPwleSizeMax();
+            int segmentCount = limit > 0
+                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
+                    : effect.getSegments().size();
+            List<RampSegment> pwles = new ArrayList<>();
+            for (int i = segmentIndex; i < segmentCount; i++) {
+                VibrationEffectSegment segment = effect.getSegments().get(i);
+                if (segment instanceof RampSegment) {
+                    pwles.add((RampSegment) segment);
+                } else {
+                    break;
+                }
+            }
+
+            if (pwles.isEmpty()) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
+                        + effect.getSegments().get(segmentIndex));
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+            mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
+                    getVibration().id);
+
+            return nextSteps(/* segmentsPlayed= */ pwles.size());
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
new file mode 100644
index 0000000..bbbca02
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Finish a sync vibration started by a {@link StartSequentialEffectStep}.
+ *
+ * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+ * StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet.
+ */
+final class FinishSequentialEffectStep extends Step {
+    public final StartSequentialEffectStep startedStep;
+
+    FinishSequentialEffectStep(StartSequentialEffectStep startedStep) {
+        // No predefined startTime, just wait for all steps in the queue.
+        super(startedStep.conductor, Long.MAX_VALUE);
+        this.startedStep = startedStep;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        // This step only notes that all the vibrators has been turned off.
+        return true;
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
+            }
+            conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+            Step nextStep = startedStep.nextStep();
+            return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
+                    : Arrays.asList(nextStep);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @Override
+    public List<Step> cancel() {
+        cancelImmediately();
+        return VibrationStepConductor.EMPTY_STEP_LIST;
+    }
+
+    @Override
+    public void cancelImmediately() {
+        conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
new file mode 100644
index 0000000..601ae97
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on with a single prebaked effect.
+ *
+ * <p>This step automatically falls back by replacing the prebaked segment with
+ * {@link VibrationSettings#getFallbackEffect(int)}, if available.
+ */
+final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
+
+    PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step should wait for the last vibration to finish (with the timeout) and for the
+        // intended step start time (to respect the effect delays).
+        super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect,
+                index, previousStepVibratorOffTimeout);
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep");
+        try {
+            VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+            if (!(segment instanceof PrebakedSegment)) {
+                Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a "
+                        + "PerformPrebakedVibratorStep: " + segment);
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            PrebakedSegment prebaked = (PrebakedSegment) segment;
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG, "Perform " + VibrationEffect.effectIdToString(
+                        prebaked.getEffectId()) + " on vibrator "
+                        + controller.getVibratorInfo().getId());
+            }
+
+            VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
+            mVibratorOnResult = controller.on(prebaked, getVibration().id);
+
+            if (mVibratorOnResult == 0 && prebaked.shouldFallback()
+                    && (fallback instanceof VibrationEffect.Composed)) {
+                if (VibrationThread.DEBUG) {
+                    Slog.d(VibrationThread.TAG, "Playing fallback for effect "
+                            + VibrationEffect.effectIdToString(prebaked.getEffectId()));
+                }
+                AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller,
+                        replaceCurrentSegment((VibrationEffect.Composed) fallback),
+                        segmentIndex, previousStepVibratorOffTimeout);
+                List<Step> fallbackResult = fallbackStep.play();
+                // Update the result with the fallback result so this step is seamlessly
+                // replaced by the fallback to any outer application of this.
+                mVibratorOnResult = fallbackStep.getVibratorOnDuration();
+                return fallbackResult;
+            }
+
+            return nextSteps(/* segmentsPlayed= */ 1);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    /**
+     * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
+     *
+     * @return a copy of {@link #effect} with replaced segment.
+     */
+    private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
+        List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
+        int newRepeatIndex = effect.getRepeatIndex();
+        newSegments.remove(segmentIndex);
+        newSegments.addAll(segmentIndex, fallback.getSegments());
+        if (segmentIndex < effect.getRepeatIndex()) {
+            newRepeatIndex += fallback.getSegments().size() - 1;
+        }
+        return new VibrationEffect.Composed(newSegments, newRepeatIndex);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
new file mode 100644
index 0000000..8cf5fb3
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Represents a step to ramp down the vibrator amplitude before turning it off. */
+final class RampOffVibratorStep extends AbstractVibratorStep {
+    private final float mAmplitudeTarget;
+    private final float mAmplitudeDelta;
+
+    RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget,
+            float amplitudeDelta, VibratorController controller,
+            long previousStepVibratorOffTimeout) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1,
+                previousStepVibratorOffTimeout);
+        mAmplitudeTarget = amplitudeTarget;
+        mAmplitudeDelta = amplitudeDelta;
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        return true;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(
+                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep");
+        try {
+            if (VibrationThread.DEBUG) {
+                long latency = SystemClock.uptimeMillis() - startTime;
+                Slog.d(VibrationThread.TAG, "Ramp down the vibrator amplitude, step with "
+                        + latency + "ms latency.");
+            }
+            if (mVibratorCompleteCallbackReceived) {
+                // Vibration completion callback was received by this step, just turn if off
+                // and skip the rest of the steps to ramp down the vibrator amplitude.
+                stopVibrating();
+                return VibrationStepConductor.EMPTY_STEP_LIST;
+            }
+
+            changeAmplitude(mAmplitudeTarget);
+
+            float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
+            if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) {
+                // Vibrator amplitude cannot go further down, just turn it off.
+                return Arrays.asList(new TurnOffVibratorStep(
+                        conductor, previousStepVibratorOffTimeout, controller));
+            }
+            return Arrays.asList(new RampOffVibratorStep(
+                    conductor,
+                    startTime + conductor.vibrationSettings.getRampStepDuration(),
+                    newAmplitudeTarget, mAmplitudeDelta, controller,
+                    previousStepVibratorOffTimeout));
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
new file mode 100644
index 0000000..d5c1116
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator on and change its amplitude.
+ *
+ * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
+ * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
+ */
+final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+    private long mNextOffTime;
+
+    SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller, VibrationEffect.Composed effect, int index,
+            long previousStepVibratorOffTimeout) {
+        // This step has a fixed startTime coming from the timings of the waveform it's playing.
+        super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout);
+        mNextOffTime = previousStepVibratorOffTimeout;
+    }
+
+    @Override
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        if (controller.getVibratorInfo().getId() == vibratorId) {
+            mVibratorCompleteCallbackReceived = true;
+            mNextOffTime = SystemClock.uptimeMillis();
+        }
+        // Timings are tightly controlled here, so only trigger this step if the vibrator was
+        // supposed to be ON but has completed prematurely, to turn it back on as soon as
+        // possible.
+        return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
+    }
+
+    @Override
+    public List<Step> play() {
+        // TODO: consider separating the "on" steps at the start into a separate Step.
+        // TODO: consider instantiating the step with the required amplitude, rather than
+        // needing to dig into the effect.
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep");
+        try {
+            long now = SystemClock.uptimeMillis();
+            long latency = now - startTime;
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Running amplitude step with " + latency + "ms latency.");
+            }
+
+            if (mVibratorCompleteCallbackReceived && latency < 0) {
+                // This step was run early because the vibrator turned off prematurely.
+                // Turn it back on and return this same step to run at the exact right time.
+                mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
+                return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller,
+                        effect, segmentIndex, mNextOffTime));
+            }
+
+            VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+            if (!(segment instanceof StepSegment)) {
+                Slog.w(VibrationThread.TAG,
+                        "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment);
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            StepSegment stepSegment = (StepSegment) segment;
+            if (stepSegment.getDuration() == 0) {
+                // Skip waveform entries with zero timing.
+                return skipToNextSteps(/* segmentsSkipped= */ 1);
+            }
+
+            float amplitude = stepSegment.getAmplitude();
+            if (amplitude == 0) {
+                if (previousStepVibratorOffTimeout > now) {
+                    // Amplitude cannot be set to zero, so stop the vibrator.
+                    stopVibrating();
+                    mNextOffTime = now;
+                }
+            } else {
+                if (startTime >= mNextOffTime) {
+                    // Vibrator is OFF. Turn vibrator back on for the duration of another
+                    // cycle before setting the amplitude.
+                    long onDuration = getVibratorOnDuration(effect, segmentIndex);
+                    if (onDuration > 0) {
+                        mVibratorOnResult = startVibrating(onDuration);
+                        mNextOffTime = now + onDuration
+                                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+                    }
+                }
+                changeAmplitude(amplitude);
+            }
+
+            // Use original startTime to avoid propagating latencies to the waveform.
+            long nextStartTime = startTime + segment.getDuration();
+            return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private long turnVibratorBackOn(long remainingDuration) {
+        long onDuration = getVibratorOnDuration(effect, segmentIndex);
+        if (onDuration <= 0) {
+            // Vibrator is supposed to go back off when this step starts, so just leave it off.
+            return previousStepVibratorOffTimeout;
+        }
+        onDuration += remainingDuration;
+        float expectedAmplitude = controller.getCurrentAmplitude();
+        mVibratorOnResult = startVibrating(onDuration);
+        if (mVibratorOnResult > 0) {
+            // Set the amplitude back to the value it was supposed to be playing at.
+            changeAmplitude(expectedAmplitude);
+        }
+        return SystemClock.uptimeMillis() + onDuration
+                + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+    }
+
+    private long startVibrating(long duration) {
+        if (VibrationThread.DEBUG) {
+            Slog.d(VibrationThread.TAG,
+                    "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+                            + duration + "ms");
+        }
+        return controller.on(duration, getVibration().id);
+    }
+
+    /**
+     * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
+     * until the next time it's vibrating amplitude is zero or a different type of segment is
+     * found.
+     */
+    private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
+        List<VibrationEffectSegment> segments = effect.getSegments();
+        int segmentCount = segments.size();
+        int repeatIndex = effect.getRepeatIndex();
+        int i = startIndex;
+        long timing = 0;
+        while (i < segmentCount) {
+            VibrationEffectSegment segment = segments.get(i);
+            if (!(segment instanceof StepSegment)
+                    || ((StepSegment) segment).getAmplitude() == 0) {
+                break;
+            }
+            timing += segment.getDuration();
+            i++;
+            if (i == segmentCount && repeatIndex >= 0) {
+                i = repeatIndex;
+                // prevent infinite loop
+                repeatIndex = -1;
+            }
+            if (i == startIndex) {
+                // The repeating waveform keeps the vibrator ON all the time. Use a minimum
+                // of 1s duration to prevent short patterns from turning the vibrator ON too
+                // frequently.
+                return Math.max(timing, 1000);
+            }
+        }
+        if (i == segmentCount && effect.getRepeatIndex() < 0) {
+            // Vibration ending at non-zero amplitude, add extra timings to ramp down after
+            // vibration is complete.
+            timing += conductor.vibrationSettings.getRampDownDuration();
+        }
+        return timing;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
new file mode 100644
index 0000000..b8885e8
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.Nullable;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Starts a sync vibration.
+ *
+ * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+ * add a {@link FinishSequentialEffectStep} to the queue, to be played after all vibrators
+ * have finished all their individual steps.
+ *
+ * <p>If this step does not start any vibrator, it will add a {@link StartSequentialEffectStep} if
+ * the sequential effect isn't finished yet.
+ *
+ * <p>TODO: this step actually does several things: multiple HAL calls to sync the vibrators,
+ * as well as dispatching the underlying vibrator instruction calls (which need to be done before
+ * triggering the synced effects). This role/encapsulation could probably be improved to split up
+ * the grouped HAL calls here, as well as to clarify the role of dispatching VibratorSteps between
+ * this class and the controller.
+ */
+final class StartSequentialEffectStep extends Step {
+    public final CombinedVibration.Sequential sequentialEffect;
+    public final int currentIndex;
+
+    private long mVibratorsOnMaxDuration;
+
+    StartSequentialEffectStep(VibrationStepConductor conductor,
+            CombinedVibration.Sequential effect) {
+        this(conductor, SystemClock.uptimeMillis() + effect.getDelays().get(0), effect,
+                /* index= */ 0);
+    }
+
+    StartSequentialEffectStep(VibrationStepConductor conductor, long startTime,
+            CombinedVibration.Sequential effect, int index) {
+        super(conductor, startTime);
+        sequentialEffect = effect;
+        currentIndex = index;
+    }
+
+    @Override
+    public long getVibratorOnDuration() {
+        return mVibratorsOnMaxDuration;
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep");
+        List<Step> nextSteps = new ArrayList<>();
+        mVibratorsOnMaxDuration = -1;
+        try {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "StartSequentialEffectStep for effect #" + currentIndex);
+            }
+            CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
+            DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+            if (effectMapping == null) {
+                // Unable to map effects to vibrators, ignore this step.
+                return nextSteps;
+            }
+
+            mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
+            if (mVibratorsOnMaxDuration > 0) {
+                conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+                        mVibratorsOnMaxDuration);
+            }
+        } finally {
+            if (mVibratorsOnMaxDuration >= 0) {
+                // It least one vibrator was started then add a finish step to wait for all
+                // active vibrators to finish their individual steps before going to the next.
+                // Otherwise this step was ignored so just go to the next one.
+                Step nextStep =
+                        mVibratorsOnMaxDuration > 0 ? new FinishSequentialEffectStep(this)
+                                : nextStep();
+                if (nextStep != null) {
+                    nextSteps.add(nextStep);
+                }
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+        return nextSteps;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return VibrationStepConductor.EMPTY_STEP_LIST;
+    }
+
+    @Override
+    public void cancelImmediately() {
+    }
+
+    /**
+     * Create the next {@link StartSequentialEffectStep} to play this sequential effect, starting at
+     * the
+     * time this method is called, or null if sequence is complete.
+     */
+    @Nullable
+    Step nextStep() {
+        int nextIndex = currentIndex + 1;
+        if (nextIndex >= sequentialEffect.getEffects().size()) {
+            return null;
+        }
+        long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+        long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+        return new StartSequentialEffectStep(conductor, nextStartTime, sequentialEffect,
+                nextIndex);
+    }
+
+    /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+    @Nullable
+    private DeviceEffectMap createEffectToVibratorMapping(
+            CombinedVibration effect) {
+        if (effect instanceof CombinedVibration.Mono) {
+            return new DeviceEffectMap((CombinedVibration.Mono) effect);
+        }
+        if (effect instanceof CombinedVibration.Stereo) {
+            return new DeviceEffectMap((CombinedVibration.Stereo) effect);
+        }
+        return null;
+    }
+
+    /**
+     * Starts playing effects on designated vibrators, in sync.
+     *
+     * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
+     * @param nextSteps     An output list to accumulate the future {@link Step
+     *                      Steps} created
+     *                      by this method, typically one for each vibrator that has
+     *                      successfully started vibrating on this step.
+     * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
+     * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+     * have ignored all effects.
+     */
+    private long startVibrating(
+            DeviceEffectMap effectMapping, List<Step> nextSteps) {
+        int vibratorCount = effectMapping.size();
+        if (vibratorCount == 0) {
+            // No effect was mapped to any available vibrator.
+            return 0;
+        }
+
+        AbstractVibratorStep[] steps = new AbstractVibratorStep[vibratorCount];
+        long vibrationStartTime = SystemClock.uptimeMillis();
+        for (int i = 0; i < vibratorCount; i++) {
+            steps[i] = conductor.nextVibrateStep(vibrationStartTime,
+                    conductor.getVibrators().get(effectMapping.vibratorIdAt(i)),
+                    effectMapping.effectAt(i),
+                    /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
+        }
+
+        if (steps.length == 1) {
+            // No need to prepare and trigger sync effects on a single vibrator.
+            return startVibrating(steps[0], nextSteps);
+        }
+
+        // This synchronization of vibrators should be executed one at a time, even if we are
+        // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
+        // one set of vibrators at a time.
+        // This property is guaranteed by there only being one thread (VibrationThread) executing
+        // one Step at a time, so there's no need to hold the state lock. Callbacks will be
+        // delivered asynchronously but enqueued until the step processing is finished.
+        boolean hasPrepared = false;
+        boolean hasTriggered = false;
+        long maxDuration = 0;
+        try {
+            hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration(
+                    effectMapping.getRequiredSyncCapabilities(),
+                    effectMapping.getVibratorIds());
+
+            for (AbstractVibratorStep step : steps) {
+                long duration = startVibrating(step, nextSteps);
+                if (duration < 0) {
+                    // One vibrator has failed, fail this entire sync attempt.
+                    return maxDuration = -1;
+                }
+                maxDuration = Math.max(maxDuration, duration);
+            }
+
+            // Check if sync was prepared and if any step was accepted by a vibrator,
+            // otherwise there is nothing to trigger here.
+            if (hasPrepared && maxDuration > 0) {
+                hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
+                        getVibration().id);
+            }
+            return maxDuration;
+        } finally {
+            if (hasPrepared && !hasTriggered) {
+                // Trigger has failed or all steps were ignored by the vibrators.
+                conductor.vibratorManagerHooks.cancelSyncedVibration();
+                nextSteps.clear();
+            } else if (maxDuration < 0) {
+                // Some vibrator failed without being prepared so other vibrators might be
+                // active. Cancel and remove every pending step from output list.
+                for (int i = nextSteps.size() - 1; i >= 0; i--) {
+                    nextSteps.remove(i).cancelImmediately();
+                }
+            }
+        }
+    }
+
+    private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) {
+        nextSteps.addAll(step.play());
+        long stepDuration = step.getVibratorOnDuration();
+        if (stepDuration < 0) {
+            // Step failed, so return negative duration to propagate failure.
+            return stepDuration;
+        }
+        // Return the longest estimation for the entire effect.
+        return Math.max(stepDuration, step.effect.getDuration());
+    }
+
+    /**
+     * Map a {@link CombinedVibration} to the vibrators available on the device.
+     *
+     * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+     * play all of the effects in sync.
+     */
+    final class DeviceEffectMap {
+        private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
+        private final int[] mVibratorIds;
+        private final long mRequiredSyncCapabilities;
+
+        DeviceEffectMap(CombinedVibration.Mono mono) {
+            SparseArray<VibratorController> vibrators = conductor.getVibrators();
+            mVibratorEffects = new SparseArray<>(vibrators.size());
+            mVibratorIds = new int[vibrators.size()];
+            for (int i = 0; i < vibrators.size(); i++) {
+                int vibratorId = vibrators.keyAt(i);
+                VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+                VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+                        mono.getEffect(), vibratorInfo);
+                if (effect instanceof VibrationEffect.Composed) {
+                    mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+                    mVibratorIds[i] = vibratorId;
+                }
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        DeviceEffectMap(CombinedVibration.Stereo stereo) {
+            SparseArray<VibratorController> vibrators = conductor.getVibrators();
+            SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+            mVibratorEffects = new SparseArray<>();
+            for (int i = 0; i < stereoEffects.size(); i++) {
+                int vibratorId = stereoEffects.keyAt(i);
+                if (vibrators.contains(vibratorId)) {
+                    VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo();
+                    VibrationEffect effect = conductor.deviceEffectAdapter.apply(
+                            stereoEffects.valueAt(i), vibratorInfo);
+                    if (effect instanceof VibrationEffect.Composed) {
+                        mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
+                    }
+                }
+            }
+            mVibratorIds = new int[mVibratorEffects.size()];
+            for (int i = 0; i < mVibratorEffects.size(); i++) {
+                mVibratorIds[i] = mVibratorEffects.keyAt(i);
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        /**
+         * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
+         * device.
+         */
+        public int size() {
+            return mVibratorIds.length;
+        }
+
+        /**
+         * Return all capabilities required to play the {@link CombinedVibration} in
+         * between calls to {@link IVibratorManager#prepareSynced} and
+         * {@link IVibratorManager#triggerSynced}.
+         */
+        public long getRequiredSyncCapabilities() {
+            return mRequiredSyncCapabilities;
+        }
+
+        /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
+        public int[] getVibratorIds() {
+            return mVibratorIds;
+        }
+
+        /** Return the id of the vibrator at given index. */
+        public int vibratorIdAt(int index) {
+            return mVibratorEffects.keyAt(index);
+        }
+
+        /** Return the {@link VibrationEffect} at given index. */
+        public VibrationEffect.Composed effectAt(int index) {
+            return mVibratorEffects.valueAt(index);
+        }
+
+        /**
+         * Return all capabilities required from the {@link IVibratorManager} to prepare and
+         * trigger all given effects in sync.
+         *
+         * @return {@link IVibratorManager#CAP_SYNC} together with all required
+         * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
+         */
+        private long calculateRequiredSyncCapabilities(
+                SparseArray<VibrationEffect.Composed> effects) {
+            long prepareCap = 0;
+            for (int i = 0; i < effects.size(); i++) {
+                VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
+                if (firstSegment instanceof StepSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_ON;
+                } else if (firstSegment instanceof PrebakedSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
+                } else if (firstSegment instanceof PrimitiveSegment) {
+                    prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
+                }
+            }
+            int triggerCap = 0;
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
+            }
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
+            }
+            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
+                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
+            }
+            return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
+        }
+
+        /**
+         * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
+         * different ones, requiring a mixed trigger capability from the vibrator manager for
+         * syncing all effects.
+         */
+        private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
+            return (prepareCapabilities & capability) != 0
+                    && (prepareCapabilities & ~capability) != 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java
new file mode 100644
index 0000000..042e8a0
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/Step.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.os.CombinedVibration;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+
+import java.util.List;
+
+/**
+ * Represent a single step for playing a vibration.
+ *
+ * <p>Every step has a start time, which can be used to apply delays between steps while
+ * executing them in sequence.
+ */
+abstract class Step implements Comparable<Step> {
+    public final VibrationStepConductor conductor;
+    public final long startTime;
+
+    Step(VibrationStepConductor conductor, long startTime) {
+        this.conductor = conductor;
+        this.startTime = startTime;
+    }
+
+    protected Vibration getVibration() {
+        return conductor.getVibration();
+    }
+
+    /**
+     * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
+     * {@link CombinedVibration}.
+     */
+    public boolean isCleanUp() {
+        return false;
+    }
+
+    /** Play this step, returning a (possibly empty) list of next steps. */
+    @NonNull
+    public abstract List<Step> play();
+
+    /**
+     * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
+     * be played to gracefully cancel this step.
+     */
+    @NonNull
+    public abstract List<Step> cancel();
+
+    /** Cancel this pending step immediately, skipping any clean-up. */
+    public abstract void cancelImmediately();
+
+    /**
+     * Return the duration the vibrator was turned on when this step was played.
+     *
+     * @return A positive duration that the vibrator was turned on for by this step;
+     * Zero if the segment is not supported, the step was not played yet or vibrator was never
+     * turned on by this step; A negative value if the vibrator call has failed.
+     */
+    public long getVibratorOnDuration() {
+        return 0;
+    }
+
+    /**
+     * Return true to run this step right after a vibrator has notified vibration completed,
+     * used to resume steps waiting on vibrator callbacks with a timeout.
+     */
+    public boolean acceptVibratorCompleteCallback(int vibratorId) {
+        return false;
+    }
+
+    /**
+     * Returns the time in millis to wait before playing this step. This is performed
+     * while holding the queue lock, so should not rely on potentially slow operations.
+     */
+    public long calculateWaitTime() {
+        if (startTime == Long.MAX_VALUE) {
+            // This step don't have a predefined start time, it's just marked to be executed
+            // after all other steps have finished.
+            return 0;
+        }
+        return Math.max(0, startTime - SystemClock.uptimeMillis());
+    }
+
+    @Override
+    public int compareTo(Step o) {
+        return Long.compare(startTime, o.startTime);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
new file mode 100644
index 0000000..297ef56
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a step to turn the vibrator off.
+ *
+ * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+ * and can be brought forward by vibrator complete callbacks. The step shouldn't be skipped, even
+ * if the vibrator-complete callback was received, as some implementations still rely on the
+ * "off" call to actually stop.
+ */
+final class TurnOffVibratorStep extends AbstractVibratorStep {
+
+    TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
+            VibratorController controller) {
+        super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+    }
+
+    @Override
+    public boolean isCleanUp() {
+        return true;
+    }
+
+    @Override
+    public List<Step> cancel() {
+        return Arrays.asList(
+                new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+    }
+
+    @Override
+    public void cancelImmediately() {
+        stopVibrating();
+    }
+
+    @Override
+    public List<Step> play() {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep");
+        try {
+            stopVibrating();
+            return VibrationStepConductor.EMPTY_STEP_LIST;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
new file mode 100644
index 0000000..51691fb
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
+import android.os.VibrationEffect;
+import android.os.WorkSource;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
+ * dispatch of callbacks.
+ */
+final class VibrationStepConductor {
+    /**
+     * Extra timeout added to the end of each vibration step to ensure it finishes even when
+     * vibrator callbacks are lost.
+     */
+    static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
+    /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
+    static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
+    static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
+    final Object mLock = new Object();
+
+    // Used within steps.
+    public final VibrationSettings vibrationSettings;
+    public final DeviceVibrationEffectAdapter deviceEffectAdapter;
+    public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+
+    private final WorkSource mWorkSource;
+    private final Vibration mVibration;
+    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
+    @GuardedBy("mLock")
+    private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
+    @GuardedBy("mLock")
+    private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
+
+    @GuardedBy("mLock")
+    private int mPendingVibrateSteps;
+    @GuardedBy("mLock")
+    private int mConsumedStartVibrateSteps;
+    @GuardedBy("mLock")
+    private int mSuccessfulVibratorOnSteps;
+    @GuardedBy("mLock")
+    private boolean mWaitToProcessVibratorCompleteCallbacks;
+
+    VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
+            DeviceVibrationEffectAdapter effectAdapter,
+            SparseArray<VibratorController> availableVibrators,
+            VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
+        this.mVibration = vib;
+        this.vibrationSettings = vibrationSettings;
+        this.deviceEffectAdapter = effectAdapter;
+        this.vibratorManagerHooks = vibratorManagerHooks;
+        this.mWorkSource = new WorkSource(mVibration.uid);
+
+        CombinedVibration effect = vib.getEffect();
+        for (int i = 0; i < availableVibrators.size(); i++) {
+            if (effect.hasVibrator(availableVibrators.keyAt(i))) {
+                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
+            }
+        }
+    }
+
+    @Nullable
+    AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller,
+            VibrationEffect.Composed effect, int segmentIndex,
+            long previousStepVibratorOffTimeout) {
+        if (segmentIndex >= effect.getSegments().size()) {
+            segmentIndex = effect.getRepeatIndex();
+        }
+        if (segmentIndex < 0) {
+            // No more segments to play, last step is to complete the vibration on this vibrator.
+            return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false,
+                    controller, previousStepVibratorOffTimeout);
+        }
+
+        VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
+        if (segment instanceof PrebakedSegment) {
+            return new PerformPrebakedVibratorStep(this, startTime, controller, effect,
+                    segmentIndex, previousStepVibratorOffTimeout);
+        }
+        if (segment instanceof PrimitiveSegment) {
+            return new ComposePrimitivesVibratorStep(this, startTime, controller, effect,
+                    segmentIndex, previousStepVibratorOffTimeout);
+        }
+        if (segment instanceof RampSegment) {
+            return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex,
+                    previousStepVibratorOffTimeout);
+        }
+        return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex,
+                previousStepVibratorOffTimeout);
+    }
+
+    public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
+        synchronized (mLock) {
+            mPendingVibrateSteps++;
+            mNextSteps.offer(new StartSequentialEffectStep(this, vibration));
+        }
+    }
+
+    public Vibration getVibration() {
+        return mVibration;
+    }
+
+    public WorkSource getWorkSource() {
+        return mWorkSource;
+    }
+
+    SparseArray<VibratorController> getVibrators() {
+        return mVibrators;
+    }
+
+    public boolean isFinished() {
+        synchronized (mLock) {
+            return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
+        }
+    }
+
+    /**
+     * Calculate the {@link Vibration.Status} based on the current queue state and the expected
+     * number of {@link StartSequentialEffectStep} to be played.
+     */
+    public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
+        synchronized (mLock) {
+            if (mPendingVibrateSteps > 0
+                    || mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
+                return Vibration.Status.RUNNING;
+            }
+            if (mSuccessfulVibratorOnSteps > 0) {
+                return Vibration.Status.FINISHED;
+            }
+            // If no step was able to turn the vibrator ON successfully.
+            return Vibration.Status.IGNORED_UNSUPPORTED;
+        }
+    }
+
+    /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
+    @GuardedBy("mLock")
+    public long getWaitMillisBeforeNextStepLocked() {
+        if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+            // Steps resumed by vibrator complete callback should be played right away.
+            return 0;
+        }
+        Step nextStep = mNextSteps.peek();
+        return nextStep == null ? 0 : nextStep.calculateWaitTime();
+    }
+
+    /**
+     * Play and remove the step at the top of this queue, and also adds the next steps generated
+     * to be played next.
+     */
+    public void runNextStep() {
+        // Vibrator callbacks should wait until the polled step is played and the next steps are
+        // added back to the queue, so they can handle the callback.
+        markWaitToProcessVibratorCallbacks();
+        try {
+            Step nextStep = pollNext();
+            if (nextStep != null) {
+                // This might turn on the vibrator and have a HAL latency. Execute this outside
+                // any lock to avoid blocking other interactions with the thread.
+                List<Step> nextSteps = nextStep.play();
+                synchronized (mLock) {
+                    if (nextStep.getVibratorOnDuration() > 0) {
+                        mSuccessfulVibratorOnSteps++;
+                    }
+                    if (nextStep instanceof StartSequentialEffectStep) {
+                        mConsumedStartVibrateSteps++;
+                    }
+                    if (!nextStep.isCleanUp()) {
+                        mPendingVibrateSteps--;
+                    }
+                    for (int i = 0; i < nextSteps.size(); i++) {
+                        mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
+                    }
+                    mNextSteps.addAll(nextSteps);
+                }
+            }
+        } finally {
+            synchronized (mLock) {
+                processVibratorCompleteCallbacksLocked();
+            }
+        }
+    }
+
+    /**
+     * Notify the vibrator completion.
+     *
+     * <p>This is a lightweight method that do not trigger any operation from {@link
+     * VibratorController}, so it can be called directly from a native callback.
+     */
+    @GuardedBy("mLock")
+    private void notifyVibratorCompleteLocked(int vibratorId) {
+        mCompletionNotifiedVibrators.offer(vibratorId);
+        if (!mWaitToProcessVibratorCompleteCallbacks) {
+            // No step is being played or cancelled now, process the callback right away.
+            processVibratorCompleteCallbacksLocked();
+        }
+    }
+
+    public void notifyVibratorComplete(int vibratorId) {
+        synchronized (mLock) {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Vibration complete reported by vibrator " + vibratorId);
+            }
+            notifyVibratorCompleteLocked(vibratorId);
+            mLock.notify();
+        }
+    }
+
+    public void notifySyncedVibrationComplete() {
+        synchronized (mLock) {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Synced vibration complete reported by vibrator manager");
+            }
+            for (int i = 0; i < mVibrators.size(); i++) {
+                notifyVibratorCompleteLocked(mVibrators.keyAt(i));
+            }
+            mLock.notify();
+        }
+    }
+
+    /**
+     * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
+     *
+     * <p>This will remove all steps and replace them with respective
+     * {@link Step#cancel()}.
+     */
+    public void cancel() {
+        // Vibrator callbacks should wait until all steps from the queue are properly cancelled
+        // and clean up steps are added back to the queue, so they can handle the callback.
+        markWaitToProcessVibratorCallbacks();
+        try {
+            List<Step> cleanUpSteps = new ArrayList<>();
+            Step step;
+            while ((step = pollNext()) != null) {
+                cleanUpSteps.addAll(step.cancel());
+            }
+            synchronized (mLock) {
+                // All steps generated by Step.cancel() should be clean-up steps.
+                mPendingVibrateSteps = 0;
+                mNextSteps.addAll(cleanUpSteps);
+            }
+        } finally {
+            synchronized (mLock) {
+                processVibratorCompleteCallbacksLocked();
+            }
+        }
+    }
+
+    /**
+     * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
+     *
+     * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
+     */
+    public void cancelImmediately() {
+        // Vibrator callbacks should wait until all steps from the queue are properly cancelled.
+        markWaitToProcessVibratorCallbacks();
+        try {
+            Step step;
+            while ((step = pollNext()) != null) {
+                // This might turn off the vibrator and have a HAL latency. Execute this outside
+                // any lock to avoid blocking other interactions with the thread.
+                step.cancelImmediately();
+            }
+            synchronized (mLock) {
+                mPendingVibrateSteps = 0;
+            }
+        } finally {
+            synchronized (mLock) {
+                processVibratorCompleteCallbacksLocked();
+            }
+        }
+    }
+
+    @Nullable
+    private Step pollNext() {
+        synchronized (mLock) {
+            // Prioritize the steps resumed by a vibrator complete callback.
+            if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
+                return mPendingOnVibratorCompleteSteps.poll();
+            }
+            return mNextSteps.poll();
+        }
+    }
+
+    private void markWaitToProcessVibratorCallbacks() {
+        synchronized (mLock) {
+            mWaitToProcessVibratorCompleteCallbacks = true;
+        }
+    }
+
+    /**
+     * Notify the step in this queue that should be resumed by the vibrator completion
+     * callback and keep it separate to be consumed by {@link #runNextStep()}.
+     *
+     * <p>This is a lightweight method that do not trigger any operation from {@link
+     * VibratorController}, so it can be called directly from a native callback.
+     *
+     * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+     * first step found will be resumed by this method, in no particular order.
+     */
+    @GuardedBy("mLock")
+    private void processVibratorCompleteCallbacksLocked() {
+        mWaitToProcessVibratorCompleteCallbacks = false;
+        while (!mCompletionNotifiedVibrators.isEmpty()) {
+            int vibratorId = mCompletionNotifiedVibrators.poll();
+            Iterator<Step> it = mNextSteps.iterator();
+            while (it.hasNext()) {
+                Step step = it.next();
+                if (step.acceptVibratorCompleteCallback(vibratorId)) {
+                    it.remove();
+                    mPendingOnVibratorCompleteSteps.offer(step);
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 1f1f40b..f2cd8c3 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,59 +16,23 @@
 
 package com.android.server.vibrator;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.vibrator.IVibratorManager;
 import android.os.CombinedVibration;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.VibratorInfo;
-import android.os.WorkSource;
-import android.os.vibrator.PrebakedSegment;
-import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.RampSegment;
-import android.os.vibrator.StepSegment;
-import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.FrameworkStatsLog;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.NoSuchElementException;
-import java.util.PriorityQueue;
-import java.util.Queue;
 
 /** Plays a {@link Vibration} in dedicated thread. */
 final class VibrationThread extends Thread implements IBinder.DeathRecipient {
-    private static final String TAG = "VibrationThread";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Extra timeout added to the end of each vibration step to ensure it finishes even when
-     * vibrator callbacks are lost.
-     */
-    private static final long CALLBACKS_EXTRA_TIMEOUT = 1_000;
-
-    /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */
-    private static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f;
-
-    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
-    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
-
-    private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+    static final String TAG = "VibrationThread";
+    static final boolean DEBUG = false;
 
     /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
     interface VibratorManagerHooks {
@@ -94,6 +58,15 @@
         void cancelSyncedVibration();
 
         /**
+         * Record that a vibrator was turned on, and may remain on for the specified duration,
+         * on behalf of the given uid.
+         */
+        void noteVibratorOn(int uid, long duration);
+
+        /** Record that a vibrator was turned off, on behalf of the given uid. */
+        void noteVibratorOff(int uid);
+
+        /**
          * Tell the manager that the currently active vibration has completed its vibration, from
          * the perspective of the Effect. However, the VibrationThread may still be continuing with
          * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
@@ -108,16 +81,10 @@
         void onVibrationThreadReleased();
     }
 
-    private final Object mLock = new Object();
-    private final WorkSource mWorkSource;
     private final PowerManager.WakeLock mWakeLock;
-    private final IBatteryStats mBatteryStatsService;
-    private final VibrationSettings mVibrationSettings;
-    private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
-    private final Vibration mVibration;
-    private final VibratorManagerHooks mVibratorManagerHooks;
-    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
-    private final StepQueue mStepQueue = new StepQueue();
+    private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks;
+
+    private final VibrationStepConductor mStepConductor;
 
     private volatile boolean mStop;
     private volatile boolean mForceStop;
@@ -127,30 +94,20 @@
     VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
             DeviceVibrationEffectAdapter effectAdapter,
             SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
-            IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) {
-        mVibration = vib;
-        mVibrationSettings = vibrationSettings;
-        mDeviceEffectAdapter = effectAdapter;
+            VibratorManagerHooks vibratorManagerHooks) {
         mVibratorManagerHooks = vibratorManagerHooks;
-        mWorkSource = new WorkSource(mVibration.uid);
         mWakeLock = wakeLock;
-        mBatteryStatsService = batteryStatsService;
-
-        CombinedVibration effect = vib.getEffect();
-        for (int i = 0; i < availableVibrators.size(); i++) {
-            if (effect.hasVibrator(availableVibrators.keyAt(i))) {
-                mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
-            }
-        }
+        mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter,
+                availableVibrators, vibratorManagerHooks);
     }
 
     Vibration getVibration() {
-        return mVibration;
+        return mStepConductor.getVibration();
     }
 
     @VisibleForTesting
     SparseArray<VibratorController> getVibrators() {
-        return mVibrators;
+        return mStepConductor.getVibrators();
     }
 
     @Override
@@ -179,7 +136,7 @@
 
     /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
     private void runWithWakeLock() {
-        mWakeLock.setWorkSource(mWorkSource);
+        mWakeLock.setWorkSource(mStepConductor.getWorkSource());
         mWakeLock.acquire();
         try {
             runWithWakeLockAndDeathLink();
@@ -193,8 +150,9 @@
      * Called from within runWithWakeLock.
      */
     private void runWithWakeLockAndDeathLink() {
+        IBinder vibrationBinderToken = mStepConductor.getVibration().token;
         try {
-            mVibration.token.linkToDeath(this, 0);
+            vibrationBinderToken.linkToDeath(this, 0);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error linking vibration to token death", e);
             clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
@@ -206,7 +164,7 @@
             playVibration();
         } finally {
             try {
-                mVibration.token.unlinkToDeath(this, 0);
+                vibrationBinderToken.unlinkToDeath(this, 0);
             } catch (NoSuchElementException e) {
                 Slog.wtf(TAG, "Failed to unlink token", e);
             }
@@ -220,11 +178,11 @@
             return;
         }
         mStop = true;
-        synchronized (mLock) {
+        synchronized (mStepConductor.mLock) {
             if (DEBUG) {
                 Slog.d(TAG, "Vibration cancelled");
             }
-            mLock.notify();
+            mStepConductor.mLock.notify();
         }
     }
 
@@ -235,36 +193,22 @@
             return;
         }
         mStop = mForceStop = true;
-        synchronized (mLock) {
+        synchronized (mStepConductor.mLock) {
             if (DEBUG) {
                 Slog.d(TAG, "Vibration cancelled immediately");
             }
-            mLock.notify();
+            mStepConductor.mLock.notify();
         }
     }
 
     /** Notify current vibration that a synced step has completed. */
     public void syncedVibrationComplete() {
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
-            }
-            for (int i = 0; i < mVibrators.size(); i++) {
-                mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i));
-            }
-            mLock.notify();
-        }
+        mStepConductor.notifySyncedVibrationComplete();
     }
 
     /** Notify current vibration that a step has completed on given vibrator. */
     public void vibratorComplete(int vibratorId) {
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
-            }
-            mStepQueue.notifyVibratorComplete(vibratorId);
-            mLock.notify();
-        }
+        mStepConductor.notifyVibratorComplete(vibratorId);
     }
 
     // Indicate that the vibration is complete. This can be called multiple times only for
@@ -273,52 +217,55 @@
     private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
         if (!mCalledVibrationCompleteCallback) {
             mCalledVibrationCompleteCallback = true;
-            mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus);
+            mVibratorManagerHooks.onVibrationCompleted(
+                    mStepConductor.getVibration().id, completedStatus);
         }
     }
 
     private void playVibration() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
         try {
-            CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
+            CombinedVibration.Sequential sequentialEffect =
+                    toSequential(mStepConductor.getVibration().getEffect());
             final int sequentialEffectSize = sequentialEffect.getEffects().size();
-            mStepQueue.initializeForEffect(sequentialEffect);
+            mStepConductor.initializeForEffect(sequentialEffect);
 
-            while (!mStepQueue.isFinished()) {
+            while (!mStepConductor.isFinished()) {
                 long waitMillisBeforeNextStep;
-                synchronized (mLock) {
-                    waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep();
+                synchronized (mStepConductor.mLock) {
+                    waitMillisBeforeNextStep = mStepConductor.getWaitMillisBeforeNextStepLocked();
                     if (waitMillisBeforeNextStep > 0) {
                         try {
-                            mLock.wait(waitMillisBeforeNextStep);
+                            mStepConductor.mLock.wait(waitMillisBeforeNextStep);
                         } catch (InterruptedException e) {
                         }
                     }
                 }
                 // Only run the next vibration step if we didn't have to wait in this loop.
-                // If we waited then the queue may have changed, so loop again to re-evaluate
-                // the scheduling of the queue top element.
+                // If we waited then the queue may have changed or the wait could have been
+                // interrupted by a cancel call, so loop again to re-evaluate the scheduling of
+                // the queue top element.
                 if (waitMillisBeforeNextStep <= 0) {
                     if (DEBUG) {
                         Slog.d(TAG, "Play vibration consuming next step...");
                     }
                     // Run the step without holding the main lock, to avoid HAL interactions from
                     // blocking the thread.
-                    mStepQueue.runNextStep();
+                    mStepConductor.runNextStep();
                 }
                 Vibration.Status status = mStop ? Vibration.Status.CANCELLED
-                        : mStepQueue.calculateVibrationStatus(sequentialEffectSize);
+                        : mStepConductor.calculateVibrationStatus(sequentialEffectSize);
                 if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
                     // First time vibration stopped running, start clean-up tasks and notify
                     // callback immediately.
                     clientVibrationCompleteIfNotAlready(status);
                     if (status == Vibration.Status.CANCELLED) {
-                        mStepQueue.cancel();
+                        mStepConductor.cancel();
                     }
                 }
                 if (mForceStop) {
                     // Cancel every step and stop playing them right away, even clean-up steps.
-                    mStepQueue.cancelImmediately();
+                    mStepConductor.cancelImmediately();
                     clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
                     break;
                 }
@@ -328,61 +275,6 @@
         }
     }
 
-    private void noteVibratorOn(long duration) {
-        try {
-            if (duration <= 0) {
-                return;
-            }
-            if (duration == Long.MAX_VALUE) {
-                // Repeating duration has started. Report a fixed duration here, noteVibratorOff
-                // should be called when this is cancelled.
-                duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
-            }
-            mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
-                    duration);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private void noteVibratorOff() {
-        try {
-            mBatteryStatsService.noteVibratorOff(mVibration.uid);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
-                    /* duration= */ 0);
-        } catch (RemoteException e) {
-        }
-    }
-
-    @Nullable
-    private SingleVibratorStep nextVibrateStep(long startTime, VibratorController controller,
-            VibrationEffect.Composed effect, int segmentIndex, long vibratorOffTimeout) {
-        if (segmentIndex >= effect.getSegments().size()) {
-            segmentIndex = effect.getRepeatIndex();
-        }
-        if (segmentIndex < 0) {
-            // No more segments to play, last step is to complete the vibration on this vibrator.
-            return new EffectCompleteStep(startTime, /* cancelled= */ false, controller,
-                    vibratorOffTimeout);
-        }
-
-        VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-        if (segment instanceof PrebakedSegment) {
-            return new PerformStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
-        }
-        if (segment instanceof PrimitiveSegment) {
-            return new ComposePrimitivesStep(startTime, controller, effect, segmentIndex,
-                    vibratorOffTimeout);
-        }
-        if (segment instanceof RampSegment) {
-            return new ComposePwleStep(startTime, controller, effect, segmentIndex,
-                    vibratorOffTimeout);
-        }
-        return new AmplitudeStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout);
-    }
-
     private static CombinedVibration.Sequential toSequential(CombinedVibration effect) {
         if (effect instanceof CombinedVibration.Sequential) {
             return (CombinedVibration.Sequential) effect;
@@ -392,1268 +284,4 @@
                 .combine();
     }
 
-    /** Queue for {@link Step Steps}, sorted by their start time. */
-    private final class StepQueue {
-        @GuardedBy("mLock")
-        private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
-        @GuardedBy("mLock")
-        private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
-        @GuardedBy("mLock")
-        private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
-
-        @GuardedBy("mLock")
-        private int mPendingVibrateSteps;
-        @GuardedBy("mLock")
-        private int mConsumedStartVibrateSteps;
-        @GuardedBy("mLock")
-        private int mSuccessfulVibratorOnSteps;
-        @GuardedBy("mLock")
-        private boolean mWaitToProcessVibratorCompleteCallbacks;
-
-        public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
-            synchronized (mLock) {
-                mPendingVibrateSteps++;
-                mNextSteps.offer(new StartVibrateStep(vibration));
-            }
-        }
-
-        public boolean isFinished() {
-            synchronized (mLock) {
-                return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
-            }
-        }
-
-        /**
-         * Calculate the {@link Vibration.Status} based on the current queue state and the expected
-         * number of {@link StartVibrateStep} to be played.
-         */
-        public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) {
-            synchronized (mLock) {
-                if (mPendingVibrateSteps > 0
-                        || mConsumedStartVibrateSteps < expectedStartVibrateSteps) {
-                    return Vibration.Status.RUNNING;
-                }
-                if (mSuccessfulVibratorOnSteps > 0) {
-                    return Vibration.Status.FINISHED;
-                }
-                // If no step was able to turn the vibrator ON successfully.
-                return Vibration.Status.IGNORED_UNSUPPORTED;
-            }
-        }
-
-        /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
-        @GuardedBy("VibrationThread.this.mLock")
-        public long getWaitMillisBeforeNextStep() {
-            if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
-                // Steps resumed by vibrator complete callback should be played right away.
-                return 0;
-            }
-            Step nextStep = mNextSteps.peek();
-            return nextStep == null ? 0 : nextStep.calculateWaitTime();
-        }
-
-        /**
-         * Play and remove the step at the top of this queue, and also adds the next steps generated
-         * to be played next.
-         */
-        public void runNextStep() {
-            // Vibrator callbacks should wait until the polled step is played and the next steps are
-            // added back to the queue, so they can handle the callback.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                Step nextStep = pollNext();
-                if (nextStep != null) {
-                    // This might turn on the vibrator and have a HAL latency. Execute this outside
-                    // any lock to avoid blocking other interactions with the thread.
-                    List<Step> nextSteps = nextStep.play();
-                    synchronized (mLock) {
-                        if (nextStep.getVibratorOnDuration() > 0) {
-                            mSuccessfulVibratorOnSteps++;
-                        }
-                        if (nextStep instanceof StartVibrateStep) {
-                            mConsumedStartVibrateSteps++;
-                        }
-                        if (!nextStep.isCleanUp()) {
-                            mPendingVibrateSteps--;
-                        }
-                        for (int i = 0; i < nextSteps.size(); i++) {
-                            mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1;
-                        }
-                        mNextSteps.addAll(nextSteps);
-                    }
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        /**
-         * Notify the vibrator completion.
-         *
-         * <p>This is a lightweight method that do not trigger any operation from {@link
-         * VibratorController}, so it can be called directly from a native callback.
-         */
-        @GuardedBy("mLock")
-        public void notifyVibratorComplete(int vibratorId) {
-            mCompletionNotifiedVibrators.offer(vibratorId);
-            if (!mWaitToProcessVibratorCompleteCallbacks) {
-                // No step is being played or cancelled now, process the callback right away.
-                processVibratorCompleteCallbacks();
-            }
-        }
-
-        /**
-         * Cancel the current queue, replacing all remaining steps with respective clean-up steps.
-         *
-         * <p>This will remove all steps and replace them with respective
-         * {@link Step#cancel()}.
-         */
-        public void cancel() {
-            // Vibrator callbacks should wait until all steps from the queue are properly cancelled
-            // and clean up steps are added back to the queue, so they can handle the callback.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                List<Step> cleanUpSteps = new ArrayList<>();
-                Step step;
-                while ((step = pollNext()) != null) {
-                    cleanUpSteps.addAll(step.cancel());
-                }
-                synchronized (mLock) {
-                    // All steps generated by Step.cancel() should be clean-up steps.
-                    mPendingVibrateSteps = 0;
-                    mNextSteps.addAll(cleanUpSteps);
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        /**
-         * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up.
-         *
-         * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
-         */
-        public void cancelImmediately() {
-            // Vibrator callbacks should wait until all steps from the queue are properly cancelled.
-            markWaitToProcessVibratorCallbacks();
-            try {
-                Step step;
-                while ((step = pollNext()) != null) {
-                    // This might turn off the vibrator and have a HAL latency. Execute this outside
-                    // any lock to avoid blocking other interactions with the thread.
-                    step.cancelImmediately();
-                }
-                synchronized (mLock) {
-                    mPendingVibrateSteps = 0;
-                }
-            } finally {
-                synchronized (mLock) {
-                    processVibratorCompleteCallbacks();
-                }
-            }
-        }
-
-        @Nullable
-        private Step pollNext() {
-            synchronized (mLock) {
-                // Prioritize the steps resumed by a vibrator complete callback.
-                if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
-                    return mPendingOnVibratorCompleteSteps.poll();
-                }
-                return mNextSteps.poll();
-            }
-        }
-
-        private void markWaitToProcessVibratorCallbacks() {
-            synchronized (mLock) {
-                mWaitToProcessVibratorCompleteCallbacks = true;
-            }
-        }
-
-        /**
-         * Notify the step in this queue that should be resumed by the vibrator completion
-         * callback and keep it separate to be consumed by {@link #runNextStep()}.
-         *
-         * <p>This is a lightweight method that do not trigger any operation from {@link
-         * VibratorController}, so it can be called directly from a native callback.
-         *
-         * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
-         * first step found will be resumed by this method, in no particular order.
-         */
-        @GuardedBy("mLock")
-        private void processVibratorCompleteCallbacks() {
-            mWaitToProcessVibratorCompleteCallbacks = false;
-            while (!mCompletionNotifiedVibrators.isEmpty()) {
-                int vibratorId = mCompletionNotifiedVibrators.poll();
-                Iterator<Step> it = mNextSteps.iterator();
-                while (it.hasNext()) {
-                    Step step = it.next();
-                    if (step.acceptVibratorCompleteCallback(vibratorId)) {
-                        it.remove();
-                        mPendingOnVibratorCompleteSteps.offer(step);
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Represent a single step for playing a vibration.
-     *
-     * <p>Every step has a start time, which can be used to apply delays between steps while
-     * executing them in sequence.
-     */
-    private abstract class Step implements Comparable<Step> {
-        public final long startTime;
-
-        Step(long startTime) {
-            this.startTime = startTime;
-        }
-
-        /**
-         * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or
-         * {@link CombinedVibration}.
-         */
-        public boolean isCleanUp() {
-            return false;
-        }
-
-        /** Play this step, returning a (possibly empty) list of next steps. */
-        @NonNull
-        public abstract List<Step> play();
-
-        /**
-         * Cancel this pending step and return a (possibly empty) list of clean-up steps that should
-         * be played to gracefully cancel this step.
-         */
-        @NonNull
-        public abstract List<Step> cancel();
-
-        /** Cancel this pending step immediately, skipping any clean-up. */
-        public abstract void cancelImmediately();
-
-        /**
-         * Return the duration the vibrator was turned on when this step was played.
-         *
-         * @return A positive duration that the vibrator was turned on for by this step;
-         * Zero if the segment is not supported, the step was not played yet or vibrator was never
-         * turned on by this step; A negative value if the vibrator call has failed.
-         */
-        public long getVibratorOnDuration() {
-            return 0;
-        }
-
-        /**
-         * Return true to run this step right after a vibrator has notified vibration completed,
-         * used to resume steps waiting on vibrator callbacks with a timeout.
-         */
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            return false;
-        }
-
-        /**
-         * Returns the time in millis to wait before playing this step. This is performed
-         * while holding the queue lock, so should not rely on potentially slow operations.
-         */
-        public long calculateWaitTime() {
-            if (startTime == Long.MAX_VALUE) {
-                // This step don't have a predefined start time, it's just marked to be executed
-                // after all other steps have finished.
-                return 0;
-            }
-            return Math.max(0, startTime - SystemClock.uptimeMillis());
-        }
-
-        @Override
-        public int compareTo(Step o) {
-            return Long.compare(startTime, o.startTime);
-        }
-    }
-
-    /**
-     * Starts a sync vibration.
-     *
-     * <p>If this step has successfully started playing a vibration on any vibrator, it will always
-     * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
-     * all their individual steps.
-     *
-     * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
-     * sequential effect isn't finished yet.
-     */
-    private final class StartVibrateStep extends Step {
-        public final CombinedVibration.Sequential sequentialEffect;
-        public final int currentIndex;
-
-        private long mVibratorsOnMaxDuration;
-
-        StartVibrateStep(CombinedVibration.Sequential effect) {
-            this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
-        }
-
-        StartVibrateStep(long startTime, CombinedVibration.Sequential effect, int index) {
-            super(startTime);
-            sequentialEffect = effect;
-            currentIndex = index;
-        }
-
-        @Override
-        public long getVibratorOnDuration() {
-            return mVibratorsOnMaxDuration;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
-            List<Step> nextSteps = new ArrayList<>();
-            mVibratorsOnMaxDuration = -1;
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
-                }
-                CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex);
-                DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
-                if (effectMapping == null) {
-                    // Unable to map effects to vibrators, ignore this step.
-                    return nextSteps;
-                }
-
-                mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
-                noteVibratorOn(mVibratorsOnMaxDuration);
-            } finally {
-                if (mVibratorsOnMaxDuration >= 0) {
-                    // It least one vibrator was started then add a finish step to wait for all
-                    // active vibrators to finish their individual steps before going to the next.
-                    // Otherwise this step was ignored so just go to the next one.
-                    Step nextStep =
-                            mVibratorsOnMaxDuration > 0 ? new FinishVibrateStep(this) : nextStep();
-                    if (nextStep != null) {
-                        nextSteps.add(nextStep);
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-            return nextSteps;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return EMPTY_STEP_LIST;
-        }
-
-        @Override
-        public void cancelImmediately() {
-        }
-
-        /**
-         * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
-         * time this method is called, or null if sequence is complete.
-         */
-        @Nullable
-        private Step nextStep() {
-            int nextIndex = currentIndex + 1;
-            if (nextIndex >= sequentialEffect.getEffects().size()) {
-                return null;
-            }
-            long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
-            long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
-            return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
-        }
-
-        /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
-        @Nullable
-        private DeviceEffectMap createEffectToVibratorMapping(
-                CombinedVibration effect) {
-            if (effect instanceof CombinedVibration.Mono) {
-                return new DeviceEffectMap((CombinedVibration.Mono) effect);
-            }
-            if (effect instanceof CombinedVibration.Stereo) {
-                return new DeviceEffectMap((CombinedVibration.Stereo) effect);
-            }
-            return null;
-        }
-
-        /**
-         * Starts playing effects on designated vibrators, in sync.
-         *
-         * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators
-         * @param nextSteps     An output list to accumulate the future {@link Step Steps} created
-         *                      by this method, typically one for each vibrator that has
-         *                      successfully started vibrating on this step.
-         * @return The duration, in millis, of the {@link CombinedVibration}. Repeating
-         * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
-         * have ignored all effects.
-         */
-        private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
-            int vibratorCount = effectMapping.size();
-            if (vibratorCount == 0) {
-                // No effect was mapped to any available vibrator.
-                return 0;
-            }
-
-            SingleVibratorStep[] steps = new SingleVibratorStep[vibratorCount];
-            long vibrationStartTime = SystemClock.uptimeMillis();
-            for (int i = 0; i < vibratorCount; i++) {
-                steps[i] = nextVibrateStep(vibrationStartTime,
-                        mVibrators.get(effectMapping.vibratorIdAt(i)),
-                        effectMapping.effectAt(i),
-                        /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0);
-            }
-
-            if (steps.length == 1) {
-                // No need to prepare and trigger sync effects on a single vibrator.
-                return startVibrating(steps[0], nextSteps);
-            }
-
-            // This synchronization of vibrators should be executed one at a time, even if we are
-            // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
-            // one set of vibrators at a time.
-            synchronized (mLock) {
-                boolean hasPrepared = false;
-                boolean hasTriggered = false;
-                long maxDuration = 0;
-                try {
-                    hasPrepared = mVibratorManagerHooks.prepareSyncedVibration(
-                            effectMapping.getRequiredSyncCapabilities(),
-                            effectMapping.getVibratorIds());
-
-                    for (SingleVibratorStep step : steps) {
-                        long duration = startVibrating(step, nextSteps);
-                        if (duration < 0) {
-                            // One vibrator has failed, fail this entire sync attempt.
-                            return maxDuration = -1;
-                        }
-                        maxDuration = Math.max(maxDuration, duration);
-                    }
-
-                    // Check if sync was prepared and if any step was accepted by a vibrator,
-                    // otherwise there is nothing to trigger here.
-                    if (hasPrepared && maxDuration > 0) {
-                        hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id);
-                    }
-                    return maxDuration;
-                } finally {
-                    if (hasPrepared && !hasTriggered) {
-                        // Trigger has failed or all steps were ignored by the vibrators.
-                        mVibratorManagerHooks.cancelSyncedVibration();
-                        nextSteps.clear();
-                    } else if (maxDuration < 0) {
-                        // Some vibrator failed without being prepared so other vibrators might be
-                        // active. Cancel and remove every pending step from output list.
-                        for (int i = nextSteps.size() - 1; i >= 0; i--) {
-                            nextSteps.remove(i).cancelImmediately();
-                        }
-                    }
-                }
-            }
-        }
-
-        private long startVibrating(SingleVibratorStep step, List<Step> nextSteps) {
-            nextSteps.addAll(step.play());
-            long stepDuration = step.getVibratorOnDuration();
-            if (stepDuration < 0) {
-                // Step failed, so return negative duration to propagate failure.
-                return stepDuration;
-            }
-            // Return the longest estimation for the entire effect.
-            return Math.max(stepDuration, step.effect.getDuration());
-        }
-    }
-
-    /**
-     * Finish a sync vibration started by a {@link StartVibrateStep}.
-     *
-     * <p>This only plays after all active vibrators steps have finished, and adds a {@link
-     * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
-     */
-    private final class FinishVibrateStep extends Step {
-        public final StartVibrateStep startedStep;
-
-        FinishVibrateStep(StartVibrateStep startedStep) {
-            super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
-            this.startedStep = startedStep;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            // This step only notes that all the vibrators has been turned off.
-            return true;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
-                }
-                noteVibratorOff();
-                Step nextStep = startedStep.nextStep();
-                return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        @Override
-        public List<Step> cancel() {
-            cancelImmediately();
-            return EMPTY_STEP_LIST;
-        }
-
-        @Override
-        public void cancelImmediately() {
-            noteVibratorOff();
-        }
-    }
-
-    /**
-     * Represent a step on a single vibrator that plays one or more segments from a
-     * {@link VibrationEffect.Composed} effect.
-     */
-    private abstract class SingleVibratorStep extends Step {
-        public final VibratorController controller;
-        public final VibrationEffect.Composed effect;
-        public final int segmentIndex;
-        public final long vibratorOffTimeout;
-
-        long mVibratorOnResult;
-        boolean mVibratorCompleteCallbackReceived;
-
-        /**
-         * @param startTime          The time to schedule this step in the {@link StepQueue}.
-         * @param controller         The vibrator that is playing the effect.
-         * @param effect             The effect being played in this step.
-         * @param index              The index of the next segment to be played by this step
-         * @param vibratorOffTimeout The time the vibrator is expected to complete any previous
-         *                           vibration and turn off. This is used to allow this step to be
-         *                           triggered when the completion callback is received, and can
-         *                           be used play effects back-to-back.
-         */
-        SingleVibratorStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            super(startTime);
-            this.controller = controller;
-            this.effect = effect;
-            this.segmentIndex = index;
-            this.vibratorOffTimeout = vibratorOffTimeout;
-        }
-
-        @Override
-        public long getVibratorOnDuration() {
-            return mVibratorOnResult;
-        }
-
-        @Override
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
-            mVibratorCompleteCallbackReceived |= isSameVibrator;
-            // Only activate this step if a timeout was set to wait for the vibration to complete,
-            // otherwise we are waiting for the correct time to play the next step.
-            return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis());
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(),
-                    /* cancelled= */ true, controller, vibratorOffTimeout));
-        }
-
-        @Override
-        public void cancelImmediately() {
-            if (vibratorOffTimeout > SystemClock.uptimeMillis()) {
-                // Vibrator might be running from previous steps, so turn it off while canceling.
-                stopVibrating();
-            }
-        }
-
-        void stopVibrating() {
-            if (DEBUG) {
-                Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
-            }
-            controller.off();
-        }
-
-        void changeAmplitude(float amplitude) {
-            if (DEBUG) {
-                Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
-                        + " to " + amplitude);
-            }
-            controller.setAmplitude(amplitude);
-        }
-
-        /** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */
-        public List<Step> skipToNextSteps(int segmentsSkipped) {
-            return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped);
-        }
-
-        /**
-         * Return the {@link #nextVibrateStep} with same start and off timings calculated from
-         * {@link #getVibratorOnDuration()}, jumping all played segments.
-         *
-         * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator
-         * result is non-positive, meaning the vibrator has either ignored or failed to turn on.
-         */
-        public List<Step> nextSteps(int segmentsPlayed) {
-            if (mVibratorOnResult <= 0) {
-                // Vibration was not started, so just skip the played segments and keep timings.
-                return skipToNextSteps(segmentsPlayed);
-            }
-            long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult;
-            long nextVibratorOffTimeout = nextStartTime + CALLBACKS_EXTRA_TIMEOUT;
-            return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed);
-        }
-
-        /**
-         * Return the {@link #nextVibrateStep} with given start and off timings, which might be
-         * calculated independently, jumping all played segments.
-         *
-         * <p>This should be used when the vibrator on/off state is not responsible for the steps
-         * execution timings, e.g. while playing the vibrator amplitudes.
-         */
-        public List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
-                int segmentsPlayed) {
-            Step nextStep = nextVibrateStep(nextStartTime, controller, effect,
-                    segmentIndex + segmentsPlayed, vibratorOffTimeout);
-            return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on with a single prebaked effect.
-     *
-     * <p>This step automatically falls back by replacing the prebaked segment with
-     * {@link VibrationSettings#getFallbackEffect(int)}, if available.
-     */
-    private final class PerformStep extends SingleVibratorStep {
-
-        PerformStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformStep");
-            try {
-                VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-                if (!(segment instanceof PrebakedSegment)) {
-                    Slog.w(TAG, "Ignoring wrong segment for a PerformStep: " + segment);
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                PrebakedSegment prebaked = (PrebakedSegment) segment;
-                if (DEBUG) {
-                    Slog.d(TAG, "Perform " + VibrationEffect.effectIdToString(
-                            prebaked.getEffectId()) + " on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-
-                VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId());
-                mVibratorOnResult = controller.on(prebaked, mVibration.id);
-
-                if (mVibratorOnResult == 0 && prebaked.shouldFallback()
-                        && (fallback instanceof VibrationEffect.Composed)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Playing fallback for effect "
-                                + VibrationEffect.effectIdToString(prebaked.getEffectId()));
-                    }
-                    SingleVibratorStep fallbackStep = nextVibrateStep(startTime, controller,
-                            replaceCurrentSegment((VibrationEffect.Composed) fallback),
-                            segmentIndex, vibratorOffTimeout);
-                    List<Step> fallbackResult = fallbackStep.play();
-                    // Update the result with the fallback result so this step is seamlessly
-                    // replaced by the fallback to any outer application of this.
-                    mVibratorOnResult = fallbackStep.getVibratorOnDuration();
-                    return fallbackResult;
-                }
-
-                return nextSteps(/* segmentsPlayed= */ 1);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        /**
-         * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments.
-         *
-         * @return a copy of {@link #effect} with replaced segment.
-         */
-        private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) {
-            List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments());
-            int newRepeatIndex = effect.getRepeatIndex();
-            newSegments.remove(segmentIndex);
-            newSegments.addAll(segmentIndex, fallback.getSegments());
-            if (segmentIndex < effect.getRepeatIndex()) {
-                newRepeatIndex += fallback.getSegments().size() - 1;
-            }
-            return new VibrationEffect.Composed(newSegments, newRepeatIndex);
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on using a composition of primitives.
-     *
-     * <p>This step will use the maximum supported number of consecutive segments of type
-     * {@link PrimitiveSegment} starting at the current index.
-     */
-    private final class ComposePrimitivesStep extends SingleVibratorStep {
-
-        ComposePrimitivesStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep");
-            try {
-                // Load the next PrimitiveSegments to create a single compose call to the vibrator,
-                // limited to the vibrator composition maximum size.
-                int limit = controller.getVibratorInfo().getCompositionSizeMax();
-                int segmentCount = limit > 0
-                        ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                        : effect.getSegments().size();
-                List<PrimitiveSegment> primitives = new ArrayList<>();
-                for (int i = segmentIndex; i < segmentCount; i++) {
-                    VibrationEffectSegment segment = effect.getSegments().get(i);
-                    if (segment instanceof PrimitiveSegment) {
-                        primitives.add((PrimitiveSegment) segment);
-                    } else {
-                        break;
-                    }
-                }
-
-                if (primitives.isEmpty()) {
-                    Slog.w(TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
-                            + effect.getSegments().get(segmentIndex));
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Compose " + primitives + " primitives on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-                mVibratorOnResult = controller.on(
-                        primitives.toArray(new PrimitiveSegment[primitives.size()]),
-                        mVibration.id);
-
-                return nextSteps(/* segmentsPlayed= */ primitives.size());
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represent a step turn the vibrator on using a composition of PWLE segments.
-     *
-     * <p>This step will use the maximum supported number of consecutive segments of type
-     * {@link StepSegment} or {@link RampSegment} starting at the current index.
-     */
-    private final class ComposePwleStep extends SingleVibratorStep {
-
-        ComposePwleStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step should wait for the last vibration to finish (with the timeout) and for the
-            // intended step start time (to respect the effect delays).
-            super(Math.max(startTime, vibratorOffTimeout), controller, effect, index,
-                    vibratorOffTimeout);
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep");
-            try {
-                // Load the next RampSegments to create a single composePwle call to the vibrator,
-                // limited to the vibrator PWLE maximum size.
-                int limit = controller.getVibratorInfo().getPwleSizeMax();
-                int segmentCount = limit > 0
-                        ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                        : effect.getSegments().size();
-                List<RampSegment> pwles = new ArrayList<>();
-                for (int i = segmentIndex; i < segmentCount; i++) {
-                    VibrationEffectSegment segment = effect.getSegments().get(i);
-                    if (segment instanceof RampSegment) {
-                        pwles.add((RampSegment) segment);
-                    } else {
-                        break;
-                    }
-                }
-
-                if (pwles.isEmpty()) {
-                    Slog.w(TAG, "Ignoring wrong segment for a ComposePwleStep: "
-                            + effect.getSegments().get(segmentIndex));
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Compose " + pwles + " PWLEs on vibrator "
-                            + controller.getVibratorInfo().getId());
-                }
-                mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
-                        mVibration.id);
-
-                return nextSteps(/* segmentsPlayed= */ pwles.size());
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to complete a {@link VibrationEffect}.
-     *
-     * <p>This runs right at the time the vibration is considered to end and will update the pending
-     * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
-     */
-    private final class EffectCompleteStep extends SingleVibratorStep {
-        private final boolean mCancelled;
-
-        EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller,
-                long vibratorOffTimeout) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
-            mCancelled = cancelled;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            // If the vibration was cancelled then this is just a clean up to ramp off the vibrator.
-            // Otherwise this step is part of the vibration.
-            return mCancelled;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            if (mCancelled) {
-                // Double cancelling will just turn off the vibrator right away.
-                return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-            }
-            return super.cancel();
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
-                            + " step on vibrator " + controller.getVibratorInfo().getId());
-                }
-                if (mVibratorCompleteCallbackReceived) {
-                    // Vibration completion callback was received by this step, just turn if off
-                    // and skip any clean-up.
-                    stopVibrating();
-                    return EMPTY_STEP_LIST;
-                }
-
-                float currentAmplitude = controller.getCurrentAmplitude();
-                long remainingOnDuration =
-                        vibratorOffTimeout - CALLBACKS_EXTRA_TIMEOUT - SystemClock.uptimeMillis();
-                long rampDownDuration =
-                        Math.min(remainingOnDuration, mVibrationSettings.getRampDownDuration());
-                long stepDownDuration = mVibrationSettings.getRampStepDuration();
-                if (currentAmplitude < RAMP_OFF_AMPLITUDE_MIN
-                        || rampDownDuration <= stepDownDuration) {
-                    // No need to ramp down the amplitude, just wait to turn it off.
-                    if (mCancelled) {
-                        // Vibration is completing because it was cancelled, turn off right away.
-                        stopVibrating();
-                        return EMPTY_STEP_LIST;
-                    } else {
-                        return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
-                    }
-                }
-
-                if (DEBUG) {
-                    Slog.d(TAG, "Ramping down vibrator " + controller.getVibratorInfo().getId()
-                            + " from amplitude " + currentAmplitude
-                            + " for " + rampDownDuration + "ms");
-                }
-                float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration);
-                float amplitudeTarget = currentAmplitude - amplitudeDelta;
-                long newVibratorOffTimeout = mCancelled ? rampDownDuration : vibratorOffTimeout;
-                return Arrays.asList(new RampOffStep(startTime, amplitudeTarget, amplitudeDelta,
-                        controller, newVibratorOffTimeout));
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /** Represents a step to ramp down the vibrator amplitude before turning it off. */
-    private final class RampOffStep extends SingleVibratorStep {
-        private final float mAmplitudeTarget;
-        private final float mAmplitudeDelta;
-
-        RampOffStep(long startTime, float amplitudeTarget, float amplitudeDelta,
-                VibratorController controller, long vibratorOffTimeout) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
-            mAmplitudeTarget = amplitudeTarget;
-            mAmplitudeDelta = amplitudeDelta;
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            return true;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffStep");
-            try {
-                if (DEBUG) {
-                    long latency = SystemClock.uptimeMillis() - startTime;
-                    Slog.d(TAG, "Ramp down the vibrator amplitude, step with "
-                            + latency + "ms latency.");
-                }
-                if (mVibratorCompleteCallbackReceived) {
-                    // Vibration completion callback was received by this step, just turn if off
-                    // and skip the rest of the steps to ramp down the vibrator amplitude.
-                    stopVibrating();
-                    return EMPTY_STEP_LIST;
-                }
-
-                changeAmplitude(mAmplitudeTarget);
-
-                float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta;
-                if (newAmplitudeTarget < RAMP_OFF_AMPLITUDE_MIN) {
-                    // Vibrator amplitude cannot go further down, just turn it off.
-                    return Arrays.asList(new OffStep(vibratorOffTimeout, controller));
-                }
-                return Arrays.asList(new RampOffStep(
-                        startTime + mVibrationSettings.getRampStepDuration(), newAmplitudeTarget,
-                        mAmplitudeDelta, controller, vibratorOffTimeout));
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to turn the vibrator off.
-     *
-     * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
-     * and can be brought forward by vibrator complete callbacks.
-     */
-    private final class OffStep extends SingleVibratorStep {
-
-        OffStep(long startTime, VibratorController controller) {
-            super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
-        }
-
-        @Override
-        public boolean isCleanUp() {
-            return true;
-        }
-
-        @Override
-        public List<Step> cancel() {
-            return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller));
-        }
-
-        @Override
-        public void cancelImmediately() {
-            stopVibrating();
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep");
-            try {
-                stopVibrating();
-                return EMPTY_STEP_LIST;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
-
-    /**
-     * Represents a step to turn the vibrator on and change its amplitude.
-     *
-     * <p>This step ignores vibration completion callbacks and control the vibrator on/off state
-     * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
-     */
-    private final class AmplitudeStep extends SingleVibratorStep {
-        private long mNextOffTime;
-
-        AmplitudeStep(long startTime, VibratorController controller,
-                VibrationEffect.Composed effect, int index, long vibratorOffTimeout) {
-            // This step has a fixed startTime coming from the timings of the waveform it's playing.
-            super(startTime, controller, effect, index, vibratorOffTimeout);
-            mNextOffTime = vibratorOffTimeout;
-        }
-
-        @Override
-        public boolean acceptVibratorCompleteCallback(int vibratorId) {
-            if (controller.getVibratorInfo().getId() == vibratorId) {
-                mVibratorCompleteCallbackReceived = true;
-                mNextOffTime = SystemClock.uptimeMillis();
-            }
-            // Timings are tightly controlled here, so only trigger this step if the vibrator was
-            // supposed to be ON but has completed prematurely, to turn it back on as soon as
-            // possible.
-            return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
-        }
-
-        @Override
-        public List<Step> play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
-            try {
-                long now = SystemClock.uptimeMillis();
-                long latency = now - startTime;
-                if (DEBUG) {
-                    Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
-                }
-
-                if (mVibratorCompleteCallbackReceived && latency < 0) {
-                    // This step was run early because the vibrator turned off prematurely.
-                    // Turn it back on and return this same step to run at the exact right time.
-                    mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
-                    return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
-                            segmentIndex, mNextOffTime));
-                }
-
-                VibrationEffectSegment segment = effect.getSegments().get(segmentIndex);
-                if (!(segment instanceof StepSegment)) {
-                    Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment);
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                StepSegment stepSegment = (StepSegment) segment;
-                if (stepSegment.getDuration() == 0) {
-                    // Skip waveform entries with zero timing.
-                    return skipToNextSteps(/* segmentsSkipped= */ 1);
-                }
-
-                float amplitude = stepSegment.getAmplitude();
-                if (amplitude == 0) {
-                    if (vibratorOffTimeout > now) {
-                        // Amplitude cannot be set to zero, so stop the vibrator.
-                        stopVibrating();
-                        mNextOffTime = now;
-                    }
-                } else {
-                    if (startTime >= mNextOffTime) {
-                        // Vibrator is OFF. Turn vibrator back on for the duration of another
-                        // cycle before setting the amplitude.
-                        long onDuration = getVibratorOnDuration(effect, segmentIndex);
-                        if (onDuration > 0) {
-                            mVibratorOnResult = startVibrating(onDuration);
-                            mNextOffTime = now + onDuration + CALLBACKS_EXTRA_TIMEOUT;
-                        }
-                    }
-                    changeAmplitude(amplitude);
-                }
-
-                // Use original startTime to avoid propagating latencies to the waveform.
-                long nextStartTime = startTime + segment.getDuration();
-                return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private long turnVibratorBackOn(long remainingDuration) {
-            long onDuration = getVibratorOnDuration(effect, segmentIndex);
-            if (onDuration <= 0) {
-                // Vibrator is supposed to go back off when this step starts, so just leave it off.
-                return vibratorOffTimeout;
-            }
-            onDuration += remainingDuration;
-            float expectedAmplitude = controller.getCurrentAmplitude();
-            mVibratorOnResult = startVibrating(onDuration);
-            if (mVibratorOnResult > 0) {
-                // Set the amplitude back to the value it was supposed to be playing at.
-                changeAmplitude(expectedAmplitude);
-            }
-            return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
-        }
-
-        private long startVibrating(long duration) {
-            if (DEBUG) {
-                Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
-                        + duration + "ms");
-            }
-            return controller.on(duration, mVibration.id);
-        }
-
-        /**
-         * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex}
-         * until the next time it's vibrating amplitude is zero or a different type of segment is
-         * found.
-         */
-        private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) {
-            List<VibrationEffectSegment> segments = effect.getSegments();
-            int segmentCount = segments.size();
-            int repeatIndex = effect.getRepeatIndex();
-            int i = startIndex;
-            long timing = 0;
-            while (i < segmentCount) {
-                VibrationEffectSegment segment = segments.get(i);
-                if (!(segment instanceof StepSegment)
-                        || ((StepSegment) segment).getAmplitude() == 0) {
-                    break;
-                }
-                timing += segment.getDuration();
-                i++;
-                if (i == segmentCount && repeatIndex >= 0) {
-                    i = repeatIndex;
-                    // prevent infinite loop
-                    repeatIndex = -1;
-                }
-                if (i == startIndex) {
-                    // The repeating waveform keeps the vibrator ON all the time. Use a minimum
-                    // of 1s duration to prevent short patterns from turning the vibrator ON too
-                    // frequently.
-                    return Math.max(timing, 1000);
-                }
-            }
-            if (i == segmentCount && effect.getRepeatIndex() < 0) {
-                // Vibration ending at non-zero amplitude, add extra timings to ramp down after
-                // vibration is complete.
-                timing += mVibrationSettings.getRampDownDuration();
-            }
-            return timing;
-        }
-    }
-
-    /**
-     * Map a {@link CombinedVibration} to the vibrators available on the device.
-     *
-     * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
-     * play all of the effects in sync.
-     */
-    private final class DeviceEffectMap {
-        private final SparseArray<VibrationEffect.Composed> mVibratorEffects;
-        private final int[] mVibratorIds;
-        private final long mRequiredSyncCapabilities;
-
-        DeviceEffectMap(CombinedVibration.Mono mono) {
-            mVibratorEffects = new SparseArray<>(mVibrators.size());
-            mVibratorIds = new int[mVibrators.size()];
-            for (int i = 0; i < mVibrators.size(); i++) {
-                int vibratorId = mVibrators.keyAt(i);
-                VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
-                VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo);
-                if (effect instanceof VibrationEffect.Composed) {
-                    mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
-                    mVibratorIds[i] = vibratorId;
-                }
-            }
-            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
-        }
-
-        DeviceEffectMap(CombinedVibration.Stereo stereo) {
-            SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
-            mVibratorEffects = new SparseArray<>();
-            for (int i = 0; i < stereoEffects.size(); i++) {
-                int vibratorId = stereoEffects.keyAt(i);
-                if (mVibrators.contains(vibratorId)) {
-                    VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo();
-                    VibrationEffect effect = mDeviceEffectAdapter.apply(
-                            stereoEffects.valueAt(i), vibratorInfo);
-                    if (effect instanceof VibrationEffect.Composed) {
-                        mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect);
-                    }
-                }
-            }
-            mVibratorIds = new int[mVibratorEffects.size()];
-            for (int i = 0; i < mVibratorEffects.size(); i++) {
-                mVibratorIds[i] = mVibratorEffects.keyAt(i);
-            }
-            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
-        }
-
-        /**
-         * Return the number of vibrators mapped to play the {@link CombinedVibration} on this
-         * device.
-         */
-        public int size() {
-            return mVibratorIds.length;
-        }
-
-        /**
-         * Return all capabilities required to play the {@link CombinedVibration} in
-         * between calls to {@link IVibratorManager#prepareSynced} and
-         * {@link IVibratorManager#triggerSynced}.
-         */
-        public long getRequiredSyncCapabilities() {
-            return mRequiredSyncCapabilities;
-        }
-
-        /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */
-        public int[] getVibratorIds() {
-            return mVibratorIds;
-        }
-
-        /** Return the id of the vibrator at given index. */
-        public int vibratorIdAt(int index) {
-            return mVibratorEffects.keyAt(index);
-        }
-
-        /** Return the {@link VibrationEffect} at given index. */
-        public VibrationEffect.Composed effectAt(int index) {
-            return mVibratorEffects.valueAt(index);
-        }
-
-        /**
-         * Return all capabilities required from the {@link IVibratorManager} to prepare and
-         * trigger all given effects in sync.
-         *
-         * @return {@link IVibratorManager#CAP_SYNC} together with all required
-         * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities.
-         */
-        private long calculateRequiredSyncCapabilities(
-                SparseArray<VibrationEffect.Composed> effects) {
-            long prepareCap = 0;
-            for (int i = 0; i < effects.size(); i++) {
-                VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0);
-                if (firstSegment instanceof StepSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_ON;
-                } else if (firstSegment instanceof PrebakedSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM;
-                } else if (firstSegment instanceof PrimitiveSegment) {
-                    prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE;
-                }
-            }
-            int triggerCap = 0;
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON;
-            }
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
-            }
-            if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) {
-                triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
-            }
-            return IVibratorManager.CAP_SYNC | prepareCap | triggerCap;
-        }
-
-        /**
-         * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with
-         * different ones, requiring a mixed trigger capability from the vibrator manager for
-         * syncing all effects.
-         */
-        private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) {
-            return (prepareCapabilities & capability) != 0
-                    && (prepareCapabilities & ~capability) != 0;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 63f3af3..01f9d0b9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -42,6 +42,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCallback;
@@ -60,6 +61,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -88,6 +90,9 @@
             VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
                     | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
 
+    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
     /** Lifecycle responsible for initializing this class at the right system server phases. */
     public static class Lifecycle extends SystemService {
         private VibratorManagerService mService;
@@ -201,8 +206,7 @@
         mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                 .getSystemUiServiceComponent().getPackageName();
 
-        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
+        mBatteryStatsService = injector.getBatteryStatsService();
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
@@ -634,7 +638,7 @@
             }
 
             VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
-                    mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
+                    mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
                     mVibrationThreadCallbacks);
 
             if (mCurrentVibration == null) {
@@ -1105,6 +1109,11 @@
             return new Handler(looper);
         }
 
+        IBatteryStats getBatteryStatsService() {
+            return IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                    BatteryStats.SERVICE_NAME));
+        }
+
         VibratorController createVibratorController(int vibratorId,
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
@@ -1141,6 +1150,36 @@
         }
 
         @Override
+        public void noteVibratorOn(int uid, long duration) {
+            try {
+                if (duration <= 0) {
+                    return;
+                }
+                if (duration == Long.MAX_VALUE) {
+                    // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+                    // should be called when this is cancelled.
+                    duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
+                }
+                mBatteryStatsService.noteVibratorOn(uid, duration);
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+                        duration);
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
+        public void noteVibratorOff(int uid) {
+            try {
+                mBatteryStatsService.noteVibratorOff(uid);
+                FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                        uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+                        /* duration= */ 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
         public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
             if (DEBUG) {
                 Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ffa1a60..98c74f8 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5149,10 +5149,13 @@
             // Ensure that we do not trigger entering PiP an activity on the root pinned task
             return false;
         }
+        final boolean isTransient = opts != null && opts.getTransientLaunch();
         final Task targetRootTask = toFrontTask != null
                 ? toFrontTask.getRootTask() : toFrontActivity.getRootTask();
-        if (targetRootTask != null && targetRootTask.isActivityTypeAssistant()) {
-            // Ensure the task/activity being brought forward is not the assistant
+        if (targetRootTask != null && (targetRootTask.isActivityTypeAssistant() || isTransient)) {
+            // Ensure the task/activity being brought forward is not the assistant and is not
+            // transient. In the case of transient-launch, we want to wait until the end of the
+            // transition and only allow switch if the transient launch was committed.
             return false;
         }
         return true;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index a9add59..6a23eb5 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -310,7 +310,7 @@
         }
         final ActivityRecord activity = result.first;
         final WindowState mainWindow = result.second;
-        final Rect contentInsets = getSystemBarInsets(task.getBounds(),
+        final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
                 mainWindow.getInsetsStateWithVisibilityOverride());
         final Rect letterboxInsets = activity.getLetterboxInsets();
         InsetUtils.addInsets(contentInsets, letterboxInsets);
@@ -575,7 +575,7 @@
         final LayoutParams attrs = mainWindow.getAttrs();
         final Rect taskBounds = task.getBounds();
         final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
-        final Rect systemBarInsets = getSystemBarInsets(taskBounds, insetsState);
+        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
                 attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
                 mHighResTaskSnapshotScale, insetsState);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0b965c3..3a3103e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -459,9 +459,19 @@
                 // activity in a bad state.
                 if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
                     boolean commitVisibility = true;
-                    if (ar.getDeferHidingClient() && ar.getTask() != null) {
+                    if (ar.isVisible() && ar.getTask() != null) {
                         if (ar.pictureInPictureArgs != null
                                 && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
+                            if (mTransientLaunches != null) {
+                                for (int j = 0; j < mTransientLaunches.size(); ++j) {
+                                    if (mTransientLaunches.valueAt(j).isVisibleRequested()) {
+                                        // force enable pip-on-task-switch now that we've committed
+                                        // to actually launching to the transient activity.
+                                        ar.supportsEnterPipOnTaskSwitch = true;
+                                        break;
+                                    }
+                                }
+                            }
                             mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
                             // Avoid commit visibility to false here, or else we will get a sudden
                             // "flash" / surface going invisible for a split second.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 091d879..33e97aa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -31,20 +31,6 @@
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
-import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
-import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
-import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
-import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER;
-import static android.app.admin.DevicePolicyManager.CODE_HAS_PAIRED;
-import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED;
-import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS;
-import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_OK;
-import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
-import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
-import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
-import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -101,6 +87,20 @@
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
+import static android.app.admin.DevicePolicyManager.STATUS_ACCOUNTS_NOT_EMPTY;
+import static android.app.admin.DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE;
+import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
+import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
+import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
+import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
+import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
+import static android.app.admin.DevicePolicyManager.STATUS_OK;
+import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
+import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
+import static android.app.admin.DevicePolicyManager.STATUS_USER_HAS_PROFILE_OWNER;
+import static android.app.admin.DevicePolicyManager.STATUS_USER_NOT_RUNNING;
+import static android.app.admin.DevicePolicyManager.STATUS_USER_SETUP_COMPLETED;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -9658,7 +9658,7 @@
         final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner,
                 /* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(),
                 isAdb(caller), hasIncompatibleAccountsOrNonAdb);
-        if (code != CODE_OK) {
+        if (code != STATUS_OK) {
             throw new IllegalStateException(
                     computeProvisioningErrorString(code, deviceOwnerUserId));
         }
@@ -9666,25 +9666,25 @@
 
     private static String computeProvisioningErrorString(int code, @UserIdInt int userId) {
         switch (code) {
-            case CODE_OK:
+            case STATUS_OK:
                 return "OK";
-            case CODE_HAS_DEVICE_OWNER:
+            case STATUS_HAS_DEVICE_OWNER:
                 return "Trying to set the device owner, but device owner is already set.";
-            case CODE_USER_HAS_PROFILE_OWNER:
+            case STATUS_USER_HAS_PROFILE_OWNER:
                 return "Trying to set the device owner, but the user already has a profile owner.";
-            case CODE_USER_NOT_RUNNING:
+            case STATUS_USER_NOT_RUNNING:
                 return "User " + userId + " not running.";
-            case CODE_NOT_SYSTEM_USER:
+            case STATUS_NOT_SYSTEM_USER:
                 return "User " + userId + " is not system user.";
-            case CODE_USER_SETUP_COMPLETED:
+            case STATUS_USER_SETUP_COMPLETED:
                 return  "Cannot set the device owner if the device is already set-up.";
-            case CODE_NONSYSTEM_USER_EXISTS:
+            case STATUS_NONSYSTEM_USER_EXISTS:
                 return "Not allowed to set the device owner because there are already several"
                         + " users on the device.";
-            case CODE_ACCOUNTS_NOT_EMPTY:
+            case STATUS_ACCOUNTS_NOT_EMPTY:
                 return "Not allowed to set the device owner because there are already some accounts"
                         + " on the device.";
-            case CODE_HAS_PAIRED:
+            case STATUS_HAS_PAIRED:
                 return "Not allowed to set the device owner because this device has already "
                         + "paired.";
             default:
@@ -14159,30 +14159,30 @@
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
-        return checkProvisioningPreConditionSkipPermission(action, packageName) == CODE_OK;
+        return checkProvisioningPreconditionSkipPermission(action, packageName) == STATUS_OK;
     }
 
     @Override
-    public int checkProvisioningPreCondition(String action, String packageName) {
+    public int checkProvisioningPrecondition(String action, String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
 
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
-        return checkProvisioningPreConditionSkipPermission(action, packageName);
+        return checkProvisioningPreconditionSkipPermission(action, packageName);
     }
 
-    private int checkProvisioningPreConditionSkipPermission(String action,
+    private int checkProvisioningPreconditionSkipPermission(String action,
             String packageName) {
         if (!mHasFeature) {
             logMissingFeatureAction("Cannot check provisioning for action " + action);
-            return CODE_DEVICE_ADMIN_NOT_SUPPORTED;
+            return STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
         }
         if (!isProvisioningAllowed()) {
-            return CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
+            return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
         }
         final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName);
-        if (code != CODE_OK) {
+        if (code != STATUS_OK) {
             Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName
                     + ") failed: "
                     + computeProvisioningErrorString(code, mInjector.userHandleGetCallingUserId()));
@@ -14230,27 +14230,27 @@
             @UserIdInt int deviceOwnerUserId, @UserIdInt int callingUserId, boolean isAdb,
             boolean hasIncompatibleAccountsOrNonAdb) {
         if (mOwners.hasDeviceOwner()) {
-            return CODE_HAS_DEVICE_OWNER;
+            return STATUS_HAS_DEVICE_OWNER;
         }
         if (mOwners.hasProfileOwner(deviceOwnerUserId)) {
-            return CODE_USER_HAS_PROFILE_OWNER;
+            return STATUS_USER_HAS_PROFILE_OWNER;
         }
 
         boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
         // System user is always running in headless system user mode.
         if (!isHeadlessSystemUserMode
                 && !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
-            return CODE_USER_NOT_RUNNING;
+            return STATUS_USER_NOT_RUNNING;
         }
         if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
-            return CODE_HAS_PAIRED;
+            return STATUS_HAS_PAIRED;
         }
 
         if (isHeadlessSystemUserMode) {
             if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
                 Slogf.e(LOG_TAG, "In headless system user mode, "
                         + "device owner can only be set on headless system user.");
-                return CODE_NOT_SYSTEM_USER;
+                return STATUS_NOT_SYSTEM_USER;
             }
         }
 
@@ -14265,7 +14265,7 @@
 
                 int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1;
                 if (mUserManager.getUserCount() > maxNumberOfExistingUsers) {
-                    return CODE_NONSYSTEM_USER_EXISTS;
+                    return STATUS_NONSYSTEM_USER_EXISTS;
                 }
 
                 int currentForegroundUser = getCurrentForegroundUserId();
@@ -14274,23 +14274,23 @@
                         && currentForegroundUser == UserHandle.USER_SYSTEM) {
                     Slogf.wtf(LOG_TAG, "In headless system user mode, "
                             + "current user cannot be system user when setting device owner");
-                    return CODE_SYSTEM_USER;
+                    return STATUS_SYSTEM_USER;
                 }
                 if (hasIncompatibleAccountsOrNonAdb) {
-                    return CODE_ACCOUNTS_NOT_EMPTY;
+                    return STATUS_ACCOUNTS_NOT_EMPTY;
                 }
             }
-            return CODE_OK;
+            return STATUS_OK;
         } else {
             // DO has to be user 0
             if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
-                return CODE_NOT_SYSTEM_USER;
+                return STATUS_NOT_SYSTEM_USER;
             }
             // Only provision DO before setup wizard completes
             if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                return CODE_USER_SETUP_COMPLETED;
+                return STATUS_USER_SETUP_COMPLETED;
             }
-            return CODE_OK;
+            return STATUS_OK;
         }
     }
 
@@ -14311,11 +14311,11 @@
     private int checkManagedProfileProvisioningPreCondition(String packageName,
             @UserIdInt int callingUserId) {
         if (!hasFeatureManagedUsers()) {
-            return CODE_MANAGED_USERS_NOT_SUPPORTED;
+            return STATUS_MANAGED_USERS_NOT_SUPPORTED;
         }
         if (getProfileOwnerAsUser(callingUserId) != null) {
             // Managed user cannot have a managed profile.
-            return CODE_USER_HAS_PROFILE_OWNER;
+            return STATUS_USER_HAS_PROFILE_OWNER;
         }
 
         final long ident = mInjector.binderClearCallingIdentity();
@@ -14334,7 +14334,7 @@
                         callingUserId);
                 // The check is called from inside a managed profile. A managed profile cannot
                 // be provisioned from within another managed profile.
-                return CODE_CANNOT_ADD_MANAGED_PROFILE;
+                return STATUS_CANNOT_ADD_MANAGED_PROFILE;
             }
 
             // If there's a device owner, the restriction on adding a managed profile must be set.
@@ -14346,19 +14346,19 @@
             if (addingProfileRestricted) {
                 Slogf.i(LOG_TAG, "Adding a profile is restricted: User %s Has device owner? %b",
                         callingUserHandle, hasDeviceOwner);
-                return CODE_CANNOT_ADD_MANAGED_PROFILE;
+                return STATUS_CANNOT_ADD_MANAGED_PROFILE;
             }
 
             // Bail out if we are trying to provision a work profile but one already exists.
             if (!mUserManager.canAddMoreManagedProfiles(
                     callingUserId, /* allowedToRemoveOne= */ false)) {
                 Slogf.i(LOG_TAG, "A work profile already exists.");
-                return CODE_CANNOT_ADD_MANAGED_PROFILE;
+                return STATUS_CANNOT_ADD_MANAGED_PROFILE;
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
-        return CODE_OK;
+        return STATUS_OK;
     }
 
     private void checkIsDeviceOwner(CallerIdentity caller) {
@@ -17697,9 +17697,9 @@
         UserInfo userInfo = null;
         final long identity = Binder.clearCallingIdentity();
         try {
-            final int result = checkProvisioningPreConditionSkipPermission(
+            final int result = checkProvisioningPreconditionSkipPermission(
                     ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName());
-            if (result != CODE_OK) {
+            if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
@@ -18076,9 +18076,9 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            int result = checkProvisioningPreConditionSkipPermission(
+            int result = checkProvisioningPreconditionSkipPermission(
                     ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
-            if (result != CODE_OK) {
+            if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 43b2e1e..dcc461b 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -265,12 +265,14 @@
         assertServiceInitialized() ?: return
         when (params.result) {
             is Result.Changed, Result.ChangedWithoutNotify -> {
-                assertThat(mockPendingBroadcasts.get(userId, params.pkgName) ?: emptyList<String>())
+                assertThat(mockPendingBroadcasts.copiedMap()?.get(userId)?.get(params.pkgName)
+                    ?: emptyList<String>())
                         .containsExactly(params.componentName!!.className)
                         .inOrder()
             }
             is Result.NotChanged, is Result.Exception -> {
-                assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull()
+                assertThat(mockPendingBroadcasts.copiedMap()?.get(userId)?.get(params.pkgName))
+                    .isNull()
             }
         }.run { /*exhaust*/ }
     }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 92cdb34..c9601de 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -20,8 +20,6 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.SigningDetails
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.verify.domain.DomainVerificationState
 import android.os.Build
@@ -30,10 +28,12 @@
 import android.util.IndentingPrintWriter
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.pm.Computer
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationEnforcer
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
 import com.android.server.pm.verify.domain.DomainVerificationService
@@ -100,11 +100,15 @@
                     mockThrowOnUnmocked {
                         whenever(callingUid) { callingUidInt.get() }
                         whenever(callingUserId) { callingUserIdInt.get() }
-                        mockPackageStates {
-                            when (it) {
-                                VISIBLE_PKG -> visiblePkgState
-                                INVISIBLE_PKG -> invisiblePkgState
-                                else -> null
+                        whenever(snapshot()) {
+                            mockThrowOnUnmocked {
+                                whenever(getPackageStateInternal(anyString())) {
+                                    when (getArgument<String>(0)) {
+                                        VISIBLE_PKG -> visiblePkgState
+                                        INVISIBLE_PKG -> invisiblePkgState
+                                        else -> null
+                                    }
+                                }
                             }
                         }
                         whenever(schedule(anyInt(), any()))
@@ -211,9 +215,8 @@
                     printState(mock(IndentingPrintWriter::class.java), null, null)
                 },
                 service(Type.QUERENT, "printStateInternal") {
-                    printState(mock(IndentingPrintWriter::class.java), null, null) {
-                        mockPkgState(it, UUID.randomUUID())
-                    }
+                    printState(mock(Computer::class.java), mock(IndentingPrintWriter::class.java),
+                        null, null)
                 },
                 service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 878bee0..7273b0b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -19,9 +19,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainOwner
 import android.content.pm.verify.domain.DomainVerificationInfo
 import android.content.pm.verify.domain.DomainVerificationManager
@@ -34,7 +31,9 @@
 import android.util.SparseArray
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates
+import com.android.server.pm.pkg.PackageUserStateInternal
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationManagerStub
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mockThrowOnUnmocked
@@ -502,8 +501,12 @@
                 whenever(callingUid) { Process.ROOT_UID }
                 whenever(callingUserId) { 0 }
 
-                mockPackageStates {
-                    pkgStateFunction(it)
+                whenever(snapshot()) {
+                    mockThrowOnUnmocked {
+                        whenever(getPackageStateInternal(anyString())) {
+                            pkgStateFunction(getArgument(0))
+                        }
+                    }
                 }
             })
         }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 0369bab..40f37a7 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -20,9 +20,6 @@
 import android.content.pm.PackageManager
 import android.content.pm.Signature
 import android.content.pm.SigningDetails
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.PackageUserStateInternal
 import android.content.pm.verify.domain.DomainOwner
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
@@ -39,9 +36,12 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import android.util.Xml
+import com.android.server.pm.Computer
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates
+import com.android.server.pm.pkg.PackageUserStateInternal
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mock
 import com.android.server.testutils.mockThrowOnUnmocked
@@ -194,7 +194,8 @@
         """
 
         val service = makeService(pkg1, pkg2)
-        service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
+        val computer = mockComputer(pkg1, pkg2)
+        service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
         service.addPackage(pkg1)
         val info = service.getInfo(pkg1.packageName)
         assertThat(info.packageName).isEqualTo(pkg1.packageName)
@@ -243,7 +244,8 @@
         """
 
         val service = makeService(pkg1, pkg2)
-        service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream()))
+        val computer = mockComputer(pkg1, pkg2)
+        service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
         service.addPackage(pkg1)
         val info = service.getInfo(pkg1.packageName)
         assertThat(info.packageName).isEqualTo(pkg1.packageName)
@@ -298,8 +300,9 @@
         """.trimIndent()
 
         val service = makeService(pkg1, pkg2)
+        val computer = mockComputer(pkg1, pkg2)
         xml.byteInputStream().use {
-            service.readSettings(Xml.resolvePullParser(it))
+            service.readSettings(computer, Xml.resolvePullParser(it))
         }
 
         service.addPackage(pkg1)
@@ -311,8 +314,9 @@
     fun addPackagePendingStripInvalidDomains() {
         val xml = addPackagePendingOrRestoredWithInvalidDomains()
         val service = makeService(pkg1, pkg2)
+        val computer = mockComputer(pkg1, pkg2)
         xml.byteInputStream().use {
-            service.readSettings(Xml.resolvePullParser(it))
+            service.readSettings(computer, Xml.resolvePullParser(it))
         }
 
         service.addPackage(pkg1)
@@ -334,8 +338,9 @@
     fun addPackageRestoredStripInvalidDomains() {
         val xml = addPackagePendingOrRestoredWithInvalidDomains()
         val service = makeService(pkg1, pkg2)
+        val computer = mockComputer(pkg1, pkg2)
         xml.byteInputStream().use {
-            service.restoreSettings(Xml.resolvePullParser(it))
+            service.restoreSettings(computer, Xml.resolvePullParser(it))
         }
 
         service.addPackage(pkg1)
@@ -686,6 +691,7 @@
         val pkg2 = mockPkgState(PKG_TWO, UUID_TWO, SIGNATURE_TWO,
             listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
         val serviceBefore = makeService(pkg1, pkg2)
+        val computerBefore = mockComputer(pkg1, pkg2)
         serviceBefore.addPackage(pkg1)
         serviceBefore.addPackage(pkg2)
 
@@ -729,16 +735,17 @@
         assertExpectedState(serviceBefore)
 
         val backupUser0 = ByteArrayOutputStream().use {
-            serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0)
+            serviceBefore.writeSettings(computerBefore, Xml.resolveSerializer(it), true, 0)
             it.toByteArray()
         }
 
         val backupUser1 = ByteArrayOutputStream().use {
-            serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 10)
+            serviceBefore.writeSettings(computerBefore, Xml.resolveSerializer(it), true, 10)
             it.toByteArray()
         }
 
         val serviceAfter = makeService(pkg1, pkg2)
+        val computerAfter = mockComputer(pkg1, pkg2)
         serviceAfter.addPackage(pkg1)
         serviceAfter.addPackage(pkg2)
 
@@ -763,7 +770,7 @@
         }
 
         ByteArrayInputStream(backupUser1).use {
-            serviceAfter.restoreSettings(Xml.resolvePullParser(it))
+            serviceAfter.restoreSettings(computerAfter, Xml.resolvePullParser(it))
         }
 
         // Assert user 1 was restored
@@ -800,7 +807,7 @@
         )
 
         ByteArrayInputStream(backupUser0).use {
-            serviceAfter.restoreSettings(Xml.resolvePullParser(it))
+            serviceAfter.restoreSettings(computerAfter, Xml.resolvePullParser(it))
         }
 
         assertExpectedState(serviceAfter)
@@ -848,12 +855,20 @@
                 whenever(callingUid) { Process.ROOT_UID }
                 whenever(callingUserId) { 0 }
 
-                mockPackageStates {
-                    pkgStateFunction(it)
-                }
+                whenever(snapshot()) { mockComputer(pkgStateFunction) }
             })
         }
 
+    private fun mockComputer(vararg pkgStates: PackageStateInternal) =
+        mockComputer { pkgName -> pkgStates.find { pkgName == it.packageName } }
+
+    private fun mockComputer(pkgStateFunction: (String) -> PackageStateInternal? = { null }) =
+        mockThrowOnUnmocked<Computer> {
+            whenever(getPackageStateInternal(anyString())) {
+                pkgStateFunction(getArgument(0))
+            }
+        }
+
     private fun mockPkgState(
         pkgName: String,
         domainSetId: UUID,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 3a602a8..fc20c26 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -29,7 +29,6 @@
 import android.util.SparseArray
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
@@ -245,10 +244,14 @@
         mockThrowOnUnmocked {
             whenever(callingUid) { TEST_UID }
             whenever(callingUserId) { TEST_USER_ID }
-            mockPackageStates {
-                when (it) {
-                    TEST_PKG -> mockPkgState()
-                    else -> null
+            whenever(snapshot()) {
+                mockThrowOnUnmocked {
+                    whenever(getPackageStateInternal(anyString())) {
+                        when (getArgument<String>(0)) {
+                            TEST_PKG -> mockPkgState()
+                            else -> null
+                        }
+                    }
                 }
             }
             whenever(schedule(anyInt(), any()))
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt
deleted file mode 100644
index c5f0eb1..0000000
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm.test.verify.domain
-
-import com.android.internal.util.FunctionalUtils
-import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
-import com.android.server.testutils.whenever
-import org.mockito.ArgumentMatchers.any
-import java.util.function.Consumer
-import java.util.function.Function
-
-internal object DomainVerificationTestUtils {
-
-    @Suppress("UNCHECKED_CAST")
-    fun DomainVerificationManagerInternal.Connection.mockPackageStates(
-        block: (String) -> PackageStateInternal?
-    ) {
-        whenever(withPackageSettingsSnapshot(any())) {
-            (arguments[0] as Consumer<Function<String, PackageStateInternal?>>).accept { block(it) }
-        }
-        whenever(withPackageSettingsSnapshotReturning(any())) {
-            (arguments[0] as FunctionalUtils.ThrowingFunction<
-                    Function<String, PackageStateInternal?>, *>)
-                .apply { block(it) }
-        }
-        whenever(withPackageSettingsSnapshotThrowing<Exception>(any())) {
-            (arguments[0] as FunctionalUtils.ThrowingCheckedConsumer<
-                    Function<String, PackageStateInternal?>, *>)
-                .accept { block(it) }
-        }
-        whenever(withPackageSettingsSnapshotThrowing2<Exception, Exception>(any())) {
-            (arguments[0] as FunctionalUtils.ThrowingChecked2Consumer<
-                    Function<String, PackageStateInternal?>, *, *>)
-                .accept { block(it) }
-        }
-        whenever(withPackageSettingsSnapshotReturningThrowing<Any, Exception>(any())) {
-            (arguments[0] as FunctionalUtils.ThrowingCheckedFunction<
-                    Function<String, PackageStateInternal?>, *, *>)
-                .apply { block(it) }
-        }
-    }
-}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index ffc2877..589633c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -31,7 +31,6 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
@@ -85,11 +84,15 @@
                 // Need to provide an internal UID so some permission checks are ignored
                 whenever(callingUid) { Process.ROOT_UID }
                 whenever(callingUserId) { 0 }
-                mockPackageStates {
-                    when (it) {
-                        PKG_ONE -> pkg1
-                        PKG_TWO -> pkg2
-                        else -> null
+                whenever(snapshot()) {
+                    mockThrowOnUnmocked {
+                        whenever(getPackageStateInternal(anyString())) {
+                            when (getArgument<String>(0)) {
+                                PKG_ONE -> pkg1
+                                PKG_TWO -> pkg2
+                                else -> null
+                            }
+                        }
                     }
                 }
             })
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
index 1c480ee..03eff2a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java
@@ -20,13 +20,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
@@ -35,11 +40,13 @@
 
 import com.android.internal.util.ConcurrentUtils;
 import com.android.server.SystemService;
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -61,10 +68,14 @@
             new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceA");
     private static final ComponentName PROVIDER_A_SERVICE_B =
             new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceB");
+    private static final ComponentName PROVIDER_A_SERVICE_C =
+            new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceC");
 
     private MockitoSession mMockingSession;
     private GameServiceController mGameServiceManager;
     @Mock
+    private Context mMockContext;
+    @Mock
     private GameServiceProviderSelector mMockGameServiceProviderSelector;
     @Mock
     private GameServiceProviderInstanceFactory mMockGameServiceProviderInstanceFactory;
@@ -77,7 +88,7 @@
                 .startMocking();
 
         mGameServiceManager = new GameServiceController(
-                ConcurrentUtils.DIRECT_EXECUTOR,
+                mMockContext, ConcurrentUtils.DIRECT_EXECUTOR,
                 mMockGameServiceProviderSelector,
                 mMockGameServiceProviderInstanceFactory);
     }
@@ -96,25 +107,30 @@
 
     @Test
     public void notifyUserStarted_createsAndStartsNewInstance() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         FakeGameServiceProviderInstance instanceA =
                 seedConfigurationForUser(USER_10, configurationA);
 
         mGameServiceManager.onBootComplete();
         mGameServiceManager.notifyUserStarted(USER_10);
 
-        verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationA.getGameServiceComponentConfiguration());
         verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
         assertThat(instanceA.getIsRunning()).isTrue();
     }
 
     @Test
     public void notifyUserStarted_sameUser_doesNotCreateNewInstance() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         FakeGameServiceProviderInstance instanceA =
                 seedConfigurationForUser(USER_10, configurationA);
 
@@ -122,16 +138,19 @@
         mGameServiceManager.notifyUserStarted(USER_10);
         mGameServiceManager.notifyUserStarted(USER_10);
 
-        verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationA.getGameServiceComponentConfiguration());
         verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
         assertThat(instanceA.getIsRunning()).isTrue();
     }
 
     @Test
     public void notifyUserUnlocking_noForegroundUser_ignores() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         FakeGameServiceProviderInstance instanceA =
                 seedConfigurationForUser(USER_10, configurationA);
 
@@ -144,9 +163,11 @@
 
     @Test
     public void notifyUserUnlocking_sameAsForegroundUser_evaluatesProvider() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         seedNoConfigurationForUser(USER_10);
 
         mGameServiceManager.onBootComplete();
@@ -155,16 +176,19 @@
                 seedConfigurationForUser(USER_10, configurationA);
         mGameServiceManager.notifyUserUnlocking(USER_10);
 
-        verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationA.getGameServiceComponentConfiguration());
         verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
         assertThat(instanceA.getIsRunning()).isTrue();
     }
 
     @Test
     public void notifyUserUnlocking_differentFromForegroundUser_ignores() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         seedNoConfigurationForUser(USER_10);
 
         mGameServiceManager.onBootComplete();
@@ -180,14 +204,18 @@
     @Test
     public void
             notifyNewForegroundUser_differentUser_stopsPreviousInstanceAndThenStartsNewInstance() {
-        GameServiceProviderConfiguration configurationA =
-                new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         FakeGameServiceProviderInstance instanceA =
                 seedConfigurationForUser(USER_10, configurationA);
-        GameServiceProviderConfiguration configurationB =
-                new GameServiceProviderConfiguration(USER_HANDLE_11, PROVIDER_A_SERVICE_A,
-                        PROVIDER_A_SERVICE_B);
+        GameServiceConfiguration configurationB =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_11,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
         FakeGameServiceProviderInstance instanceB = seedConfigurationForUser(USER_11,
                 configurationB);
         InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB);
@@ -196,8 +224,50 @@
         mGameServiceManager.notifyUserStarted(USER_10);
         mGameServiceManager.notifyNewForegroundUser(USER_11);
 
-        verify(mMockGameServiceProviderInstanceFactory).create(configurationA);
-        verify(mMockGameServiceProviderInstanceFactory).create(configurationB);
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationA.getGameServiceComponentConfiguration());
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationB.getGameServiceComponentConfiguration());
+        instancesInOrder.verify(instanceA).start();
+        instancesInOrder.verify(instanceA).stop();
+        instancesInOrder.verify(instanceB).start();
+        verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory);
+        assertThat(instanceA.getIsRunning()).isFalse();
+        assertThat(instanceB.getIsRunning()).isTrue();
+    }
+
+    @Test
+    public void packageChanges_reevaluatesGameServiceProvider() {
+        GameServiceConfiguration configurationA =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_B));
+        FakeGameServiceProviderInstance instanceA =
+                seedConfigurationForUser(USER_10, configurationA);
+
+        mGameServiceManager.onBootComplete();
+        mGameServiceManager.notifyUserStarted(USER_10);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverArgumentCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext).registerReceiver(broadcastReceiverArgumentCaptor.capture(), any());
+
+        GameServiceConfiguration configurationB =
+                new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                PROVIDER_A_SERVICE_A,
+                                PROVIDER_A_SERVICE_C));
+        FakeGameServiceProviderInstance instanceB =
+                seedConfigurationForUser(USER_10, configurationA);
+        Intent intent = new Intent();
+        intent.setData(Uri.parse("package:" + PROVIDER_A_PACKAGE_NAME));
+        broadcastReceiverArgumentCaptor.getValue().onReceive(mMockContext, intent);
+
+        InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB);
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationA.getGameServiceComponentConfiguration());
+        verify(mMockGameServiceProviderInstanceFactory).create(
+                configurationB.getGameServiceComponentConfiguration());
         instancesInOrder.verify(instanceA).start();
         instancesInOrder.verify(instanceA).stop();
         instancesInOrder.verify(instanceB).start();
@@ -207,15 +277,16 @@
     }
 
     private void seedNoConfigurationForUser(SystemService.TargetUser user) {
-        when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(null);
+        when(mMockGameServiceProviderSelector.get(user, null)).thenReturn(null);
     }
 
     private FakeGameServiceProviderInstance seedConfigurationForUser(SystemService.TargetUser user,
-            GameServiceProviderConfiguration configuration) {
-        when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(configuration);
+            GameServiceConfiguration configuration) {
+        when(mMockGameServiceProviderSelector.get(user, null)).thenReturn(configuration);
         FakeGameServiceProviderInstance instanceForConfiguration =
                 spy(new FakeGameServiceProviderInstance());
-        when(mMockGameServiceProviderInstanceFactory.create(configuration))
+        when(mMockGameServiceProviderInstanceFactory.create(
+                configuration.getGameServiceComponentConfiguration()))
                 .thenReturn(instanceForConfiguration);
 
         return instanceForConfiguration;
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
index 23a6a49..cf9ba1e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -26,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -49,6 +49,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.SystemService;
+import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration;
 
 import com.google.common.collect.ImmutableList;
 
@@ -137,10 +138,10 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(null, null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isNull();
     }
 
     @Test
@@ -154,15 +155,16 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isNull();
     }
 
     @Test
     public void get_noSystemGameService_returnsNull()
             throws Exception {
+        seedSystemGameServicePackageName("");
         seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
                 resolveInfo(GAME_SERVICE_SERVICE_INFO));
         seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
@@ -170,14 +172,14 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isNull();
     }
 
     @Test
-    public void get_noGameServiceProvidersAvailable_returnsNull()
+    public void get_noGameServiceProvidersAvailable_returnsGameServicePackageName()
             throws Exception {
         seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
         seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10);
@@ -186,28 +188,30 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isEqualTo(
+                new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null));
     }
 
     @Test
-    public void get_gameServiceProviderHasNoMetaData_returnsNull()
+    public void get_gameServiceProviderHasNoMetaData_returnsGameServicePackageName()
             throws Exception {
         seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
         seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
                 resolveInfo(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA));
         seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT);
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isEqualTo(
+                new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null));
     }
 
     @Test
-    public void get_gameSessionServiceDoesNotExist_returnsNull()
+    public void get_gameSessionServiceDoesNotExist_returnsGameServicePackageName()
             throws Exception {
         seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
         seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
@@ -217,14 +221,15 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isEqualTo(
+                new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null));
     }
 
     @Test
-    public void get_metaDataWrongFirstTag_returnsNull() throws Exception {
+    public void get_metaDataWrongFirstTag_returnsGameServicePackageName() throws Exception {
         seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME);
         seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10,
                 resolveInfo(GAME_SERVICE_SERVICE_INFO));
@@ -233,10 +238,11 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_wrong_first_tag.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        assertThat(gameServiceProviderConfiguration).isNull();
+        assertThat(gameServiceConfiguration).isEqualTo(
+                new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null));
     }
 
     @Test
@@ -250,15 +256,17 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
-                new GameServiceProviderConfiguration(USER_HANDLE_10,
-                        GAME_SERVICE_COMPONENT,
-                        GAME_SESSION_SERVICE_COMPONENT);
-        assertThat(gameServiceProviderConfiguration).isEqualTo(
-                expectedGameServiceProviderConfiguration);
+        GameServiceConfiguration expectedGameServiceConfiguration =
+                new GameServiceConfiguration(
+                        GAME_SERVICE_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                GAME_SERVICE_COMPONENT,
+                                GAME_SESSION_SERVICE_COMPONENT));
+        assertThat(gameServiceConfiguration).isEqualTo(
+                expectedGameServiceConfiguration);
     }
 
     @Test
@@ -276,15 +284,17 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
-                new GameServiceProviderConfiguration(USER_HANDLE_10,
-                        GAME_SERVICE_B_COMPONENT,
-                        GAME_SESSION_SERVICE_COMPONENT);
-        assertThat(gameServiceProviderConfiguration).isEqualTo(
-                expectedGameServiceProviderConfiguration);
+        GameServiceConfiguration expectedGameServiceConfiguration =
+                new GameServiceConfiguration(
+                        GAME_SERVICE_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                GAME_SERVICE_B_COMPONENT,
+                                GAME_SESSION_SERVICE_COMPONENT));
+        assertThat(gameServiceConfiguration).isEqualTo(
+                expectedGameServiceConfiguration);
     }
 
     @Test
@@ -299,15 +309,17 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null);
 
-        GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
-                new GameServiceProviderConfiguration(USER_HANDLE_10,
-                        GAME_SERVICE_COMPONENT,
-                        GAME_SESSION_SERVICE_COMPONENT);
-        assertThat(gameServiceProviderConfiguration).isEqualTo(
-                expectedGameServiceProviderConfiguration);
+        GameServiceConfiguration expectedGameServiceConfiguration =
+                new GameServiceConfiguration(
+                        GAME_SERVICE_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(USER_HANDLE_10,
+                                GAME_SERVICE_COMPONENT,
+                                GAME_SESSION_SERVICE_COMPONENT));
+        assertThat(gameServiceConfiguration).isEqualTo(
+                expectedGameServiceConfiguration);
     }
 
     @Test
@@ -322,16 +334,19 @@
                 GAME_SERVICE_META_DATA_RES_ID,
                 "res/xml/game_service_metadata_valid.xml");
 
-        GameServiceProviderConfiguration gameServiceProviderConfiguration =
+        GameServiceConfiguration gameServiceConfiguration =
                 mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10),
                         GAME_SERVICE_PACKAGE_NAME);
 
-        GameServiceProviderConfiguration expectedGameServiceProviderConfiguration =
-                new GameServiceProviderConfiguration(USER_HANDLE_10,
-                        GAME_SERVICE_COMPONENT,
-                        GAME_SESSION_SERVICE_COMPONENT);
-        assertThat(gameServiceProviderConfiguration).isEqualTo(
-                expectedGameServiceProviderConfiguration);
+        GameServiceConfiguration expectedGameServiceConfiguration =
+                new GameServiceConfiguration(
+                        GAME_SERVICE_PACKAGE_NAME,
+                        new GameServiceComponentConfiguration(
+                                USER_HANDLE_10,
+                                GAME_SERVICE_COMPONENT,
+                                GAME_SESSION_SERVICE_COMPONENT));
+        assertThat(gameServiceConfiguration).isEqualTo(
+                expectedGameServiceConfiguration);
     }
 
     private void seedSystemGameServicePackageName(String gameServicePackageName) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index ca5bf20..6510cd1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -167,7 +167,7 @@
         }
         whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap)
         whenever(mocks.settings.getPackageLPr(anyString())) { mSettingsMap[getArgument<Any>(0)] }
-        whenever(mocks.settings.readLPw(nullable())) {
+        whenever(mocks.settings.readLPw(any(), nullable())) {
             mSettingsMap.putAll(mPreExistingSettings)
             !mPreExistingSettings.isEmpty()
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index f35986b..b063d22 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -120,17 +120,16 @@
         whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile)
         doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageName(any(), any())
         doAnswer {
-            it.getArgument<FunctionalUtils.ThrowingConsumer<Computer>>(0).acceptOrThrow(
-                mockThrowOnUnmocked {
-                    whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries }
-                    whenever(resolveInternalPackageName(anyString(), anyLong())) {
-                        mPms.resolveInternalPackageName(getArgument(0), getArgument(1))
-                    }
-                    whenever(getPackageStateInternal(anyString())) {
-                        mPms.getPackageStateInternal(getArgument(0))
-                    }
-                })
-        }.`when`(mPms).executeWithConsistentComputer(any())
+            mockThrowOnUnmocked<Computer> {
+                whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries }
+                whenever(resolveInternalPackageName(anyString(), anyLong())) {
+                    mPms.resolveInternalPackageName(getArgument(0), getArgument(1))
+                }
+                whenever(getPackageStateInternal(anyString())) {
+                    mPms.getPackageStateInternal(getArgument(0))
+                }
+            }
+        }.`when`(mPms).snapshotComputer()
         whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any()))
             .thenReturn(PackageManager.DELETE_SUCCEEDED)
         whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat)
@@ -206,7 +205,8 @@
 
     @Test
     fun pruneUnusedStaticSharedLibraries() {
-        mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0)
+        mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(mPms.snapshotComputer(),
+            Long.MAX_VALUE, 0)
 
         verify(mDeletePackageHelper)
             .deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any())
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index ac406b5..5230ea7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -123,8 +123,8 @@
     @Test
     fun setPackagesSuspended() {
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+        val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            targetPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
 
@@ -144,14 +144,15 @@
 
     @Test
     fun setPackagesSuspended_emptyPackageName() {
-        var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */,
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            null /* packageNames */, true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+            deviceOwnerUid)
 
         assertThat(failedNames).isNull()
 
-        failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+        failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOfNulls(0), true /* suspended */, null /* appExtras */, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
 
         assertThat(failedNames).isEmpty()
@@ -159,9 +160,10 @@
 
     @Test
     fun setPackagesSuspended_callerIsNotAllowed() {
-        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid())
+        val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID,
+            Binder.getCallingUid())
 
         assertThat(failedNames).asList().hasSize(1)
         assertThat(failedNames).asList().contains(TEST_PACKAGE_2)
@@ -169,9 +171,10 @@
 
     @Test
     fun setPackagesSuspended_callerSuspendItself() {
-        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+            deviceOwnerUid)
 
         assertThat(failedNames).asList().hasSize(1)
         assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE)
@@ -179,9 +182,10 @@
 
     @Test
     fun setPackagesSuspended_nonexistentPackage() {
-        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+            deviceOwnerUid)
 
         assertThat(failedNames).asList().hasSize(1)
         assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE)
@@ -191,8 +195,8 @@
     fun setPackagesSuspended_knownPackages() {
         val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
             INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
-        val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages,
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+        val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            knownPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
 
         assertThat(failedNames.size).isEqualTo(knownPackages.size)
@@ -204,13 +208,13 @@
     @Test
     fun setPackagesUnsuspended() {
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            targetPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
-        failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
-            false /* suspended */, null /* appExtras */, null /* launcherExtras */,
+        failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            targetPackages, false /* suspended */, null /* appExtras */, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
 
@@ -235,7 +239,7 @@
         val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
             INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
-        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
+        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(pms.snapshotComputer(),
             suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid)
 
         assertThat(results.size).isEqualTo(unsuspendables.size)
@@ -247,7 +251,7 @@
     @Test
     fun getUnsuspendablePackagesForUser_callerIsNotAllowed() {
         val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
+        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(pms.snapshotComputer(),
             suspendables, TEST_USER_ID, Binder.getCallingUid())
 
         assertThat(results.size).isEqualTo(suspendables.size)
@@ -260,8 +264,8 @@
     fun getSuspendedPackageAppExtras() {
         val appExtras = PersistableBundle()
         appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
-        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
-            true /* suspended */, appExtras, null /* launcherExtras */,
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -277,8 +281,8 @@
         val appExtras = PersistableBundle()
         appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2)
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
-            true /* suspended */, appExtras, null /* launcherExtras */,
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            targetPackages, true /* suspended */, appExtras, null /* launcherExtras */,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -291,8 +295,9 @@
         assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
 
-        suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages,
-            { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID)
+        suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+            targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+            TEST_USER_ID)
 
         testHandler.flush()
         verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
@@ -320,8 +325,8 @@
     fun getSuspendedPackageLauncherExtras() {
         val launcherExtras = PersistableBundle()
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
-        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
-            true /* suspended */, null /* appExtras */, launcherExtras,
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -334,9 +339,10 @@
 
     @Test
     fun isPackageSuspended() {
-        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+            deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
@@ -348,8 +354,8 @@
     fun getSuspendingPackage() {
         val launcherExtras = PersistableBundle()
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
-        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
-            true /* suspended */, null /* appExtras */, launcherExtras,
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
             null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -362,9 +368,10 @@
     fun getSuspendedDialogInfo() {
         val dialogInfo = SuspendDialogInfo.Builder()
             .setTitle(TEST_PACKAGE_1).build()
-        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
-            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
-            dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+            arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
+            null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+            deviceOwnerUid)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 6751b80..41d46f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -73,6 +73,7 @@
                 .startMocking();
         when(mIrs.getContext()).thenReturn(mContext);
         when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy);
+        when(mIrs.getLock()).thenReturn(mIrs);
         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class));
         mScribe = new MockScribe(mIrs);
     }
@@ -89,75 +90,75 @@
         Agent agent = new Agent(mIrs, mScribe);
         Ledger ledger = new Ledger();
 
-        doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
+        doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
         doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
 
-        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
+        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 995);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -500);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000_000L, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(-1, ledger.getCurrentBalance());
     }
 
     @Test
-    public void testRecordTransaction_MaxCirculation() {
+    public void testRecordTransaction_MaxConsumptionLimit() {
         Agent agent = new Agent(mIrs, mScribe);
         Ledger ledger = new Ledger();
 
-        doReturn(1000L).when(mIrs).getMaxCirculationLocked();
-        doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+        doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
+        doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
 
-        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
+        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 995);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -500);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 2000);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
-        assertEquals(1000, ledger.getCurrentBalance());
+        assertEquals(2500, ledger.getCurrentBalance());
 
-        // MaxCirculation can change as the battery level changes. Any already allocated ARCSs
-        // shouldn't be removed by recordTransaction().
-        doReturn(900L).when(mIrs).getMaxCirculationLocked();
+        // ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
+        // affected.
+        doReturn(900L).when(mIrs).getConsumptionLimitLocked();
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 100);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
-        assertEquals(1000, ledger.getCurrentBalance());
+        assertEquals(2600, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -50);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
-        assertEquals(950, ledger.getCurrentBalance());
+        assertEquals(2550, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -200);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
-        assertEquals(750, ledger.getCurrentBalance());
+        assertEquals(2350, ledger.getCurrentBalance());
 
-        doReturn(800L).when(mIrs).getMaxCirculationLocked();
+        doReturn(800L).when(mIrs).getConsumptionLimitLocked();
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 100);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
-        assertEquals(800, ledger.getCurrentBalance());
+        assertEquals(2450, ledger.getCurrentBalance());
     }
 
     @Test
@@ -165,33 +166,33 @@
         Agent agent = new Agent(mIrs, mScribe);
         Ledger ledger = new Ledger();
 
-        doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked();
+        doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
         doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
 
-        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5);
+        Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 995);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -500);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000, ledger.getCurrentBalance());
 
         // Shouldn't change in normal operation, but adding test case in case it does.
         doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, 500);
+        transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000, ledger.getCurrentBalance());
 
-        transaction = new Ledger.Transaction(0, 0, 0, null, -1001);
+        transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
         agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(-1, ledger.getCurrentBalance());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index b72fc23..ab29e59 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -107,20 +107,26 @@
     @Test
     public void testWriteHighLevelStateToDisk() {
         long lastReclamationTime = System.currentTimeMillis();
-        long narcsInCirculation = 2000L;
+        long remainingConsumableNarcs = 2000L;
+        long consumptionLimit = 500_000L;
 
         Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000));
+        ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0));
         // Negative ledger balance shouldn't affect the total circulation value.
         ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000));
+        ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000, 3000));
         mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
+        mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit);
+        mScribeUnderTest.adjustRemainingConsumableNarcsLocked(
+                remainingConsumableNarcs - consumptionLimit);
         mScribeUnderTest.writeImmediatelyForTesting();
 
         mScribeUnderTest.loadFromDiskLocked();
 
         assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked());
-        assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked());
+        assertEquals(remainingConsumableNarcs,
+                mScribeUnderTest.getRemainingConsumableNarcsLocked());
+        assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
     }
 
     @Test
@@ -135,9 +141,9 @@
     @Test
     public void testWritingPopulatedLedgerToDisk() {
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
+        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, -1));
+        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 12));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         mScribeUnderTest.loadFromDiskLocked();
@@ -156,11 +162,11 @@
                 addInstalledPackage(userId, pkgName);
                 final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
                 ledger.recordTransaction(new Ledger.Transaction(
-                        0, 1000L * u + l, 1, null, 51L * u + l));
+                        0, 1000L * u + l, 1, null, -51L * u + l, 50));
                 ledger.recordTransaction(new Ledger.Transaction(
-                        1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l));
+                        1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l, 0));
                 ledger.recordTransaction(new Ledger.Transaction(
-                        2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l));
+                        2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l, 0));
                 ledgers.add(userId, pkgName, ledger);
             }
         }
@@ -174,9 +180,9 @@
     @Test
     public void testDiscardLedgerFromDisk() {
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
+        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 0));
+        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 1));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         mScribeUnderTest.loadFromDiskLocked();
@@ -195,9 +201,9 @@
     public void testLoadingMissingPackageFromDisk() {
         final String pkgName = TEST_PACKAGE + ".uninstalled";
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName);
-        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
+        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2));
+        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         // Package isn't installed, so make sure it's not saved to memory after loading.
@@ -209,9 +215,9 @@
     public void testLoadingMissingUserFromDisk() {
         final int userId = TEST_USER_ID + 1;
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE);
-        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3));
+        ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
+        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1));
+        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         // User doesn't show up with any packages, so make sure nothing is saved after loading.
@@ -219,6 +225,37 @@
         assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE));
     }
 
+    @Test
+    public void testChangingConsumable() {
+        assertEquals(0, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(0, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+
+        // Limit increased, so remaining value should be adjusted as well
+        mScribeUnderTest.setConsumptionLimitLocked(1000);
+        assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(1000, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+
+        // Limit decreased below remaining, so remaining value should be adjusted as well
+        mScribeUnderTest.setConsumptionLimitLocked(500);
+        assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(500, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+
+        mScribeUnderTest.adjustRemainingConsumableNarcsLocked(-100);
+        assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(400, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+
+        // Limit increased, so remaining value should be adjusted by the difference as well
+        mScribeUnderTest.setConsumptionLimitLocked(1000);
+        assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+
+
+        // Limit decreased, but above remaining, so remaining value should left alone
+        mScribeUnderTest.setConsumptionLimitLocked(950);
+        assertEquals(950, mScribeUnderTest.getSatiatedConsumptionLimitLocked());
+        assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked());
+    }
+
     private void assertLedgersEqual(Ledger expected, Ledger actual) {
         if (expected == null) {
             assertNull(actual);
@@ -245,6 +282,7 @@
         assertEquals(expected.eventId, actual.eventId);
         assertEquals(expected.tag, actual.tag);
         assertEquals(expected.delta, actual.delta);
+        assertEquals(expected.ctp, actual.ctp);
     }
 
     private void addInstalledPackage(int userId, String pkgName) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index a9b7cfb..3ce2ed8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -52,7 +52,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
@@ -94,7 +93,6 @@
     final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
             mock(FullScreenMagnificationController.ControllerContext.class);
     final Context mMockContext = mock(Context.class);
-    final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
     final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
     final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
     private final MagnificationAnimationCallback mAnimationCallback = mock(
@@ -121,12 +119,10 @@
         // Pretending ID of the Thread associated with looper as main thread ID in controller
         when(mMockContext.getMainLooper()).thenReturn(looper);
         when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
-        when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
         when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
         when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
         when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
         when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
-        when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
         initMockWindowManager();
 
         mFullScreenMagnificationController = new FullScreenMagnificationController(
@@ -357,8 +353,8 @@
         assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5);
         assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5);
         assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
-                mConfigCaptor.capture());
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+                eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
         verify(mMockValueAnimator).start();
         verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1);
@@ -501,7 +497,7 @@
         mMessageCapturingHandler.sendAllMessages();
         MagnificationConfig config = buildConfig(1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(),
                 OTHER_MAGNIFICATION_BOUNDS.centerY());
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(OTHER_REGION),
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), eq(OTHER_REGION),
                 mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
     }
@@ -655,9 +651,9 @@
         register(displayId);
         zoomIn2xToMiddle(displayId);
         mMessageCapturingHandler.sendAllMessages();
-        reset(mMockAms);
+        reset(mRequestObserver);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId),
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class));
         assertFalse(mFullScreenMagnificationController.isMagnifying(displayId));
         assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false));
@@ -676,8 +672,8 @@
         assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback));
         mMessageCapturingHandler.sendAllMessages();
 
-        verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class),
-                any(MagnificationConfig.class));
+        verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
+                any(Region.class), any(MagnificationConfig.class));
         verify(mAnimationCallback).onResult(true);
     }
 
@@ -1072,8 +1068,8 @@
         when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
         mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
-        verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION),
-                mConfigCaptor.capture());
+        verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId),
+                eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
         Mockito.reset(mMockWindowManager);
 
@@ -1097,7 +1093,7 @@
 
         // Animation should have been restarted
         verify(mMockValueAnimator, times(2)).start();
-        verify(mMockAms, times(2)).notifyMagnificationChanged(eq(displayId),
+        verify(mRequestObserver, times(2)).onFullScreenMagnificationChanged(eq(displayId),
                 eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture());
         assertConfigEquals(newConfig, mConfigCaptor.getValue());
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2060223..0fed89b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -151,8 +151,6 @@
                 mock(FullScreenMagnificationController.ControllerContext.class);
         final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
         when(mockController.getContext()).thenReturn(mContext);
-        when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService);
-        when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
         when(mockController.getWindowManager()).thenReturn(mockWindowManager);
         when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 3fcce92..ec59090 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -145,9 +145,10 @@
                         mCallbackDelegate, mTraceManager, mScaleProvider));
         mMockConnection = new MockWindowMagnificationConnection(true);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
-        new FullScreenMagnificationControllerStubber(mScreenMagnificationController);
         mMagnificationController = new MagnificationController(mService, globalLock, mContext,
                 mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider);
+        new FullScreenMagnificationControllerStubber(mScreenMagnificationController,
+                mMagnificationController);
 
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -451,6 +452,29 @@
     }
 
     @Test
+    public void onFullScreenMagnificationChanged_fullScreenEnabled_notifyMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+
+        final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN);
+        mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
+                config.getScale(), config.getCenterX(), config.getCenterY(),
+                true, TEST_SERVICE_ID);
+
+        // The first time is triggered when setting magnification enabled. And the second time is
+        // triggered when calling setScaleAndCenter.
+        final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                MagnificationConfig.class);
+        verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY),
+                eq(FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION),
+                configCaptor.capture());
+        final MagnificationConfig actualConfig = configCaptor.getValue();
+        assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
+        assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
+        assertEquals(config.getScale(), actualConfig.getScale(), 0);
+    }
+
+    @Test
     public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
@@ -679,7 +703,7 @@
             throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
-                /* scale= */1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
+                /* scale= */ 1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
                 true, TEST_SERVICE_ID);
 
         mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
@@ -884,6 +908,8 @@
     private static class FullScreenMagnificationControllerStubber {
         private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600);
         private final FullScreenMagnificationController mScreenMagnificationController;
+        private final FullScreenMagnificationController.MagnificationInfoChangedCallback
+                mMagnificationChangedCallback;
         private boolean mIsMagnifying = false;
         private float mScale = 1.0f;
         private float mCenterX = MAGNIFICATION_REGION.getBounds().exactCenterX();
@@ -891,8 +917,10 @@
         private int mServiceId = -1;
 
         FullScreenMagnificationControllerStubber(
-                FullScreenMagnificationController screenMagnificationController) {
+                FullScreenMagnificationController screenMagnificationController,
+                FullScreenMagnificationController.MagnificationInfoChangedCallback callback) {
             mScreenMagnificationController = screenMagnificationController;
+            mMagnificationChangedCallback = callback;
             stubMethods();
         }
 
@@ -930,6 +958,14 @@
                 } else {
                     reset();
                 }
+
+
+                final MagnificationConfig config = new MagnificationConfig.Builder().setMode(
+                        MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY(
+                        mCenterY).build();
+                mMagnificationChangedCallback.onFullScreenMagnificationChanged(TEST_DISPLAY,
+                        FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION,
+                        config);
                 return true;
             };
             doAnswer(setScaleAndCenterStubAnswer).when(
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
new file mode 100644
index 0000000..b154d6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.backup.ITransportStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupTransportClientTest {
+
+    private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase {
+        public final Object mLock = new Object();
+
+        public String mNameCompletedImmediately;
+
+        @GuardedBy("mLock")
+        public AndroidFuture<String> mNameCompletedInFuture;
+
+        @Override public void name(AndroidFuture<String> name) throws RemoteException {
+            name.complete(mNameCompletedImmediately);
+        }
+        @Override public void transportDirName(AndroidFuture<String> name) throws RemoteException {
+            synchronized (mLock) {
+                mNameCompletedInFuture = name;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    @Test
+    public void testName_completesImmediately_returnsName() throws Exception {
+        TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+        binder.mNameCompletedImmediately = "fake name";
+
+        BackupTransportClient client = new BackupTransportClient(binder);
+        String name = client.name();
+
+        assertThat(name).isEqualTo("fake name");
+    }
+
+    @Test
+    public void testTransportDirName_completesLater_returnsName() throws Exception {
+        TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+        BackupTransportClient client = new BackupTransportClient(binder);
+
+        Thread thread = new Thread(() -> {
+            try {
+                String name = client.transportDirName();
+                assertThat(name).isEqualTo("fake name");
+            } catch (Exception ex) {
+                fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+            }
+        });
+
+        thread.start();
+
+        synchronized (binder.mLock) {
+            while (binder.mNameCompletedInFuture == null) {
+                binder.mLock.wait();
+            }
+            assertThat(binder.mNameCompletedInFuture.complete("fake name")).isTrue();
+        }
+
+        thread.join();
+    }
+
+    @Test
+    public void testTransportDirName_canceledBeforeCompletion_throwsException() throws Exception {
+        TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder();
+        BackupTransportClient client = new BackupTransportClient(binder);
+
+        Thread thread = new Thread(() -> {
+            try {
+                /*String name =*/ client.transportDirName();
+                fail("transportDirName should be cancelled");
+            } catch (CancellationException ex) {
+                // This is expected.
+            } catch (Exception ex) {
+                fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+            }
+        });
+
+        thread.start();
+
+        synchronized (binder.mLock) {
+            while (binder.mNameCompletedInFuture == null) {
+                binder.mLock.wait();
+            }
+            client.onBecomingUnusable();
+        }
+
+        thread.join();
+    }
+
+    private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase {
+        public final Object mLock = new Object();
+
+        public int mStatusCompletedImmediately;
+
+        @GuardedBy("mLock")
+        public ITransportStatusCallback mStatusCompletedInFuture;
+
+        @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException {
+            c.onOperationCompleteWithStatus(mStatusCompletedImmediately);
+        }
+        @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException {
+            synchronized (mLock) {
+                mStatusCompletedInFuture = c;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    @Test
+    public void testInitializeDevice_completesImmediately_returnsStatus() throws Exception {
+        TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+        binder.mStatusCompletedImmediately = 123;
+
+        BackupTransportClient client = new BackupTransportClient(binder);
+        int status = client.initializeDevice();
+
+        assertThat(status).isEqualTo(123);
+    }
+
+
+    @Test
+    public void testFinishBackup_completesLater_returnsStatus() throws Exception {
+        TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+        BackupTransportClient client = new BackupTransportClient(binder);
+
+        Thread thread = new Thread(() -> {
+            try {
+                int status = client.finishBackup();
+                assertThat(status).isEqualTo(456);
+            } catch (Exception ex) {
+                fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+            }
+        });
+
+        thread.start();
+
+        synchronized (binder.mLock) {
+            while (binder.mStatusCompletedInFuture == null) {
+                binder.mLock.wait();
+            }
+            binder.mStatusCompletedInFuture.onOperationCompleteWithStatus(456);
+        }
+
+        thread.join();
+    }
+
+    @Test
+    public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception {
+        TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
+        BackupTransportClient client = new BackupTransportClient(binder);
+
+        Thread thread = new Thread(() -> {
+            try {
+                int status = client.finishBackup();
+                assertThat(status).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+            } catch (Exception ex) {
+                fail("unexpected Exception: " + ex.getClass().getCanonicalName());
+            }
+        });
+
+        thread.start();
+
+        synchronized (binder.mLock) {
+            while (binder.mStatusCompletedInFuture == null) {
+                binder.mLock.wait();
+            }
+            client.onBecomingUnusable();
+        }
+
+        thread.join();
+    }
+
+    // Convenience layer so we only need to fake specific methods useful for each test case.
+    private static class FakeTransportBinderBase implements IBackupTransport {
+        @Override public void name(AndroidFuture<String> f) throws RemoteException {}
+        @Override public void transportDirName(AndroidFuture<String> f) throws RemoteException {}
+        @Override public void configurationIntent(AndroidFuture<Intent> f) throws RemoteException {}
+        @Override public void currentDestinationString(AndroidFuture<String> f)
+                throws RemoteException {}
+        @Override public void dataManagementIntent(AndroidFuture<Intent> f)
+                throws RemoteException {}
+        @Override public void dataManagementIntentLabel(AndroidFuture<CharSequence> f)
+                throws RemoteException {}
+        @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException {}
+        @Override public void clearBackupData(PackageInfo i, ITransportStatusCallback c)
+                throws RemoteException {}
+        @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException {}
+        @Override public void requestBackupTime(AndroidFuture<Long> f) throws RemoteException {}
+        @Override public void performBackup(PackageInfo i, ParcelFileDescriptor fd, int f,
+            ITransportStatusCallback c) throws RemoteException {}
+        @Override public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> f)
+                throws RemoteException {}
+        @Override public void getCurrentRestoreSet(AndroidFuture<Long> f) throws RemoteException {}
+        @Override public void startRestore(long t, PackageInfo[] p, ITransportStatusCallback c)
+                throws RemoteException {}
+        @Override public void nextRestorePackage(AndroidFuture<RestoreDescription> f)
+                throws RemoteException {}
+        @Override public void getRestoreData(ParcelFileDescriptor fd, ITransportStatusCallback c)
+                throws RemoteException {}
+        @Override public void finishRestore(ITransportStatusCallback c) throws RemoteException {}
+        @Override public void requestFullBackupTime(AndroidFuture<Long> f) throws RemoteException {}
+        @Override public void performFullBackup(PackageInfo i, ParcelFileDescriptor fd, int f,
+            ITransportStatusCallback c) throws RemoteException {}
+        @Override public void checkFullBackupSize(long s, ITransportStatusCallback c)
+                throws RemoteException {}
+        @Override public void sendBackupData(int n, ITransportStatusCallback c)
+                throws RemoteException {}
+        @Override public void cancelFullBackup(ITransportStatusCallback c) throws RemoteException {}
+        @Override public void isAppEligibleForBackup(PackageInfo p, boolean b,
+            AndroidFuture<Boolean> f) throws RemoteException {}
+        @Override public void getBackupQuota(String s, boolean b, AndroidFuture<Long> f)
+                throws RemoteException {}
+        @Override public void getNextFullRestoreDataChunk(ParcelFileDescriptor fd,
+            ITransportStatusCallback c) throws RemoteException {}
+        @Override public void abortFullRestore(ITransportStatusCallback c) throws RemoteException {}
+        @Override public void getTransportFlags(AndroidFuture<Integer> f) throws RemoteException {}
+        @Override public IBinder asBinder() {
+            return null;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index fe110e5..2398e36 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3575,11 +3575,11 @@
         setup_DeviceAdminFeatureOff();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+                DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
-                DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+                DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+                DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED);
     }
 
     private void setup_ManagedProfileFeatureOff() throws Exception {
@@ -3611,11 +3611,11 @@
         setup_ManagedProfileFeatureOff();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
+                DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED);
     }
 
     private void setup_firstBoot_systemUser() throws Exception {
@@ -3653,15 +3653,15 @@
 
         when(getServices().userManagerForMock.isHeadlessSystemUserMode()).thenReturn(false);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
 
         when(getServices().userManagerForMock.isHeadlessSystemUserMode()).thenReturn(true);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
     }
 
     private void setup_systemUserSetupComplete_systemUser() throws Exception {
@@ -3708,11 +3708,11 @@
         setup_systemUserSetupComplete_systemUser();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+                DevicePolicyManager.STATUS_USER_SETUP_COMPLETED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
-                DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+                DevicePolicyManager.STATUS_USER_SETUP_COMPLETED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_OK);
+                DevicePolicyManager.STATUS_OK);
     }
 
     @Test
@@ -3722,22 +3722,22 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
 
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
-                DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+                DevicePolicyManager.STATUS_HAS_DEVICE_OWNER);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
-                DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+                DevicePolicyManager.STATUS_HAS_DEVICE_OWNER);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
 
         // COMP mode NOT is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
 
         // And other DPCs can NOT provision a managed profile.
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
@@ -3759,13 +3759,13 @@
                 eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
 
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
@@ -3794,12 +3794,12 @@
 
         // We can delete the managed profile to create a new one, so provisioning is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
     }
@@ -3823,13 +3823,13 @@
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DpmMockContext.ANOTHER_PACKAGE_NAME,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
                 DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
 
         // But the device owner can still do it because it has set the restriction itself.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
     }
 
@@ -3901,7 +3901,7 @@
 
         // COMP mode is NOT allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
     }
 
     private void setup_provisionManagedProfileOneAlreadyExist_primaryUser() throws Exception {
@@ -3935,14 +3935,14 @@
         setup_provisionManagedProfileOneAlreadyExist_primaryUser();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+                DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE);
     }
 
     @Test
     public void testCheckProvisioningPreCondition_permission() {
         // GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted
         assertExpectException(SecurityException.class, /* messageRegex =*/ null,
-                () -> dpm.checkProvisioningPreCondition(
+                () -> dpm.checkProvisioningPrecondition(
                         DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package"));
     }
 
@@ -8696,7 +8696,7 @@
     private void assertCheckProvisioningPreCondition(
             String action, String packageName, int provisioningCondition) {
         assertWithMessage("checkProvisioningPreCondition(%s, %s) returning unexpected result",
-                action, packageName).that(dpm.checkProvisioningPreCondition(action, packageName))
+                action, packageName).that(dpm.checkProvisioningPrecondition(action, packageName))
                         .isEqualTo(provisioningCondition);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 6789af4..2b34bc2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -35,6 +35,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
@@ -109,6 +110,8 @@
     LegacyPermissionDataProvider mPermissionDataProvider;
     @Mock
     DomainVerificationManagerInternal mDomainVerificationManager;
+    @Mock
+    Computer computer;
 
     final ArrayMap<String, Long> mOrigFirstInstallTimes = new ArrayMap<>();
 
@@ -132,7 +135,7 @@
         /* write out files and read */
         writeOldFiles();
         Settings settings = makeSettings();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
         verifyKeySetMetaData(settings);
     }
 
@@ -143,11 +146,11 @@
         // write out files and read
         writeOldFiles();
         Settings settings = makeSettings();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
 
         // write out, read back in and verify the same
-        settings.writeLPr();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        settings.writeLPr(computer);
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
         verifyKeySetMetaData(settings);
     }
 
@@ -156,7 +159,7 @@
         // Write delegateshellthe package files and make sure they're parsed properly the first time
         writeOldFiles();
         Settings settings = makeSettings();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
 
@@ -175,12 +178,12 @@
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
         Settings settings = makeSettings();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
-        settings.writeLPr();
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        settings.writeLPr(computer);
 
         // Create Settings again to make it read from the new files
         settings = makeSettings();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
 
         PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
         assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER));
@@ -469,12 +472,12 @@
         ps2.setUsesStaticLibrariesVersions(new long[] { 34 });
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
 
-        settingsUnderTest.writeLPr();
+        settingsUnderTest.writeLPr(computer);
 
         settingsUnderTest.mPackages.clear();
         settingsUnderTest.mDisabledSysPackages.clear();
 
-        assertThat(settingsUnderTest.readLPw(createFakeUsers()), is(true));
+        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
 
         PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
         PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
@@ -534,12 +537,12 @@
         ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
 
-        settingsUnderTest.writeLPr();
+        settingsUnderTest.writeLPr(computer);
 
         settingsUnderTest.mPackages.clear();
         settingsUnderTest.mDisabledSysPackages.clear();
 
-        assertThat(settingsUnderTest.readLPw(createFakeUsers()), is(true));
+        assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true));
 
         PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1);
         PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2);
@@ -587,7 +590,7 @@
         Settings settings = makeSettings();
         final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
         watcher.register();
-        assertThat(settings.readLPw(createFakeUsers()), is(true));
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
         watcher.verifyChangeReported("readLPw");
 
         // Enable/Disable a package
diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index b1b53fc..9e986be 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -65,7 +65,12 @@
         }
 
         @Override
-        long getMaxSatiatedCirculation() {
+        long getInitialSatiatedConsumptionLimit() {
+            return 0;
+        }
+
+        @Override
+        long getHardSatiatedConsumptionLimit() {
             return 0;
         }
 
@@ -102,7 +107,7 @@
         TrendCalculator trendCalculator = new TrendCalculator();
         mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 20);
 
-        trendCalculator.reset(0, null);
+        trendCalculator.reset(0, 0, null);
         assertEquals("Expected not to cross lower threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -115,10 +120,10 @@
                 new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 1, 0))),
                 mock(AffordabilityChangeListener.class), mEconomicPolicy));
         for (ActionAffordabilityNote note : affordabilityNotes) {
-            note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
         }
 
-        trendCalculator.reset(1234, affordabilityNotes);
+        trendCalculator.reset(1234, 1234, affordabilityNotes);
         assertEquals("Expected not to cross lower threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossLowerThresholdMs());
@@ -133,32 +138,28 @@
 
         OngoingEvent[] events = new OngoingEvent[]{
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
-                        null, 1, 1),
+                        1, new EconomicPolicy.Cost(1, 4)),
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
-                        null, 2, 3),
-                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3",
-                        null, 3, -3),
+                        2, new EconomicPolicy.Cost(3, 6)),
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", 3,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 0, 3, 3)),
         };
 
-        trendCalculator.reset(0, null);
+        trendCalculator.reset(0, 100, null);
         for (OngoingEvent event : events) {
             trendCalculator.accept(event);
         }
-        assertEquals("Expected not to cross lower threshold",
-                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
-                trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals(25_000, trendCalculator.getTimeToCrossLowerThresholdMs());
         assertEquals("Expected not to cross upper threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossUpperThresholdMs());
 
         ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
-        trendCalculator.reset(1234, affordabilityNotes);
+        trendCalculator.reset(1234, 1234, affordabilityNotes);
         for (OngoingEvent event : events) {
             trendCalculator.accept(event);
         }
-        assertEquals("Expected not to cross lower threshold",
-                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
-                trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals(308_000, trendCalculator.getTimeToCrossLowerThresholdMs());
         assertEquals("Expected not to cross upper threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossUpperThresholdMs());
@@ -174,18 +175,19 @@
                 new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1000))),
                 mock(AffordabilityChangeListener.class), mEconomicPolicy));
         for (ActionAffordabilityNote note : affordabilityNotes) {
-            note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
         }
 
         // Balance is already above threshold and events are all positive delta.
         // There should be no time to report.
-        trendCalculator.reset(1234, affordabilityNotes);
+        trendCalculator.reset(1234, 1234, affordabilityNotes);
         trendCalculator.accept(
-                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
-                        null, 1, 1));
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
         trendCalculator.accept(
-                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
-                        null, 2, 3));
+                new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
+                                3, 3, 3)));
 
         assertEquals("Expected not to cross lower threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -196,16 +198,16 @@
 
         // Balance is already below threshold and events are all negative delta.
         // There should be no time to report.
-        trendCalculator.reset(1, affordabilityNotes);
+        trendCalculator.reset(1, 0, affordabilityNotes);
         trendCalculator.accept(
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
-                        null, 1, -1));
+                        1, new EconomicPolicy.Cost(1, 1)));
         trendCalculator.accept(
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
-                        null, 2, -3));
+                        2, new EconomicPolicy.Cost(3, 3)));
 
         assertEquals("Expected not to cross lower threshold",
-                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                0,
                 trendCalculator.getTimeToCrossLowerThresholdMs());
         assertEquals("Expected not to cross upper threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -213,7 +215,7 @@
     }
 
     @Test
-    public void testSimpleTrendToThreshold() {
+    public void testSimpleTrendToThreshold_Balance() {
         TrendCalculator trendCalculator = new TrendCalculator();
         mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
 
@@ -222,18 +224,19 @@
                 new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
                 mock(AffordabilityChangeListener.class), mEconomicPolicy));
         for (ActionAffordabilityNote note : affordabilityNotes) {
-            note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
         }
 
         // Balance is below threshold and events are all positive delta.
         // Should report the correct time to the upper threshold.
-        trendCalculator.reset(0, affordabilityNotes);
+        trendCalculator.reset(0, 1000, affordabilityNotes);
         trendCalculator.accept(
-                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
-                        null, 1, 1));
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
         trendCalculator.accept(
-                new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
-                        null, 2, 3));
+                new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION,
+                                3, 3, 3)));
 
         assertEquals("Expected not to cross lower threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
@@ -242,13 +245,13 @@
 
         // Balance is above the threshold and events are all negative delta.
         // Should report the correct time to the lower threshold.
-        trendCalculator.reset(40, affordabilityNotes);
+        trendCalculator.reset(40, 100, affordabilityNotes);
         trendCalculator.accept(
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
-                        null, 1, -1));
+                        1, new EconomicPolicy.Cost(1, 1)));
         trendCalculator.accept(
                 new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
-                        null, 2, -3));
+                        2, new EconomicPolicy.Cost(3, 3)));
 
         assertEquals(5_000, trendCalculator.getTimeToCrossLowerThresholdMs());
         assertEquals("Expected not to cross upper threshold",
@@ -257,6 +260,123 @@
     }
 
     @Test
+    public void testSelectCorrectThreshold_Balance() {
+        TrendCalculator trendCalculator = new TrendCalculator();
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15);
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 10);
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 5);
+
+        ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        for (ActionAffordabilityNote note : affordabilityNotes) {
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
+        }
+
+        // Balance is below threshold and events are all positive delta.
+        // Should report the correct time to the correct upper threshold.
+        trendCalculator.reset(0, 10_000, affordabilityNotes);
+        trendCalculator.accept(
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
+
+        assertEquals("Expected not to cross lower threshold",
+                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
+
+        // Balance is above the threshold and events are all negative delta.
+        // Should report the correct time to the correct lower threshold.
+        trendCalculator.reset(30, 500, affordabilityNotes);
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
+                        1, new EconomicPolicy.Cost(1, 1)));
+
+        assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals("Expected not to cross upper threshold",
+                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                trendCalculator.getTimeToCrossUpperThresholdMs());
+    }
+
+    @Test
+    public void testTrendsToBothThresholds_Balance() {
+        TrendCalculator trendCalculator = new TrendCalculator();
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
+        mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
+
+        ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        for (ActionAffordabilityNote note : affordabilityNotes) {
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
+        }
+
+        // Balance is between both thresholds and events are mixed positive/negative delta.
+        // Should report the correct time to each threshold.
+        trendCalculator.reset(35, 10_000, affordabilityNotes);
+        trendCalculator.accept(
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 3, 3, 3)));
+        trendCalculator.accept(
+                new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, 2,
+                                2, 2)));
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
+                        3, new EconomicPolicy.Cost(2, 2)));
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
+                        4, new EconomicPolicy.Cost(3, 3)));
+
+        assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
+    }
+
+    @Test
+    public void testSimpleTrendToThreshold_ConsumptionLimit() {
+        TrendCalculator trendCalculator = new TrendCalculator();
+        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
+
+        ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
+        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
+                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
+                mock(AffordabilityChangeListener.class), mEconomicPolicy));
+        for (ActionAffordabilityNote note : affordabilityNotes) {
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
+        }
+
+        // Events are all negative delta. Consumable credits will run out before app's balance.
+        // Should report the correct time to the lower threshold.
+        trendCalculator.reset(10000, 40, affordabilityNotes);
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
+                        1, new EconomicPolicy.Cost(1, 10)));
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2",
+                        2, new EconomicPolicy.Cost(3, 40)));
+
+        assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals("Expected not to cross upper threshold",
+                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                trendCalculator.getTimeToCrossUpperThresholdMs());
+    }
+
+    @Test
     public void testSelectCorrectThreshold() {
         TrendCalculator trendCalculator = new TrendCalculator();
         mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
@@ -278,68 +398,45 @@
                 new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))),
                 mock(AffordabilityChangeListener.class), mEconomicPolicy));
         for (ActionAffordabilityNote note : affordabilityNotes) {
-            note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
+            note.recalculateCosts(mEconomicPolicy, 0, "com.test.app");
         }
 
-        // Balance is below threshold and events are all positive delta.
-        // Should report the correct time to the correct upper threshold.
-        trendCalculator.reset(0, affordabilityNotes);
+        // Balance is above threshold, consumable credits is 0, and events are all positive delta.
+        // There should be no time to the upper threshold since consumable credits is the limiting
+        // factor.
+        trendCalculator.reset(10_000, 0, affordabilityNotes);
         trendCalculator.accept(
-                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
-                        null, 1, 1));
+                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1,
+                        new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1)));
 
         assertEquals("Expected not to cross lower threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossLowerThresholdMs());
-        assertEquals(5_000, trendCalculator.getTimeToCrossUpperThresholdMs());
-
-        // Balance is above the threshold and events are all negative delta.
-        // Should report the correct time to the correct lower threshold.
-        trendCalculator.reset(30, affordabilityNotes);
-        trendCalculator.accept(
-                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
-                        null, 1, -1));
-
-        assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs());
         assertEquals("Expected not to cross upper threshold",
                 TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
                 trendCalculator.getTimeToCrossUpperThresholdMs());
-    }
 
-    @Test
-    public void testTrendsToBothThresholds() {
-        TrendCalculator trendCalculator = new TrendCalculator();
-        mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20);
-        mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50);
+        // Balance is above threshold, consumable credits is low, and events are all negative delta.
+        trendCalculator.reset(10_000, 4, affordabilityNotes);
+        trendCalculator.accept(
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
+                        1, new EconomicPolicy.Cost(1, 10)));
 
-        ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>();
-        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
-                new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))),
-                mock(AffordabilityChangeListener.class), mEconomicPolicy));
-        affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of(
-                new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))),
-                mock(AffordabilityChangeListener.class), mEconomicPolicy));
-        for (ActionAffordabilityNote note : affordabilityNotes) {
-            note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app");
-        }
+        assertEquals(4000, trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals("Expected not to cross upper threshold",
+                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                trendCalculator.getTimeToCrossUpperThresholdMs());
 
-        // Balance is between both thresholds and events are mixed positive/negative delta.
-        // Should report the correct time to each threshold.
-        trendCalculator.reset(35, affordabilityNotes);
+        // Balance is above threshold, consumable credits is 0, and events are all negative delta.
+        // Time to the lower threshold should be 0 since consumable credits is already 0.
+        trendCalculator.reset(10_000, 0, affordabilityNotes);
         trendCalculator.accept(
-                new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1",
-                        null, 1, 3));
-        trendCalculator.accept(
-                new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2",
-                        null, 2, 2));
-        trendCalculator.accept(
-                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3",
-                        null, 3, -2));
-        trendCalculator.accept(
-                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4",
-                        null, 4, -3));
+                new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1",
+                        1, new EconomicPolicy.Cost(1, 10)));
 
-        assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs());
-        assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs());
+        assertEquals(0, trendCalculator.getTimeToCrossLowerThresholdMs());
+        assertEquals("Expected not to cross upper threshold",
+                TrendCalculator.WILL_NOT_CROSS_THRESHOLD,
+                trendCalculator.getTimeToCrossUpperThresholdMs());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
index 4a25323..22dcf84 100644
--- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
@@ -54,13 +54,13 @@
     @Test
     public void testMultipleTransactions() {
         final Ledger ledger = new Ledger();
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5));
+        ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5, 0));
         assertEquals(5, ledger.getCurrentBalance());
         assertEquals(5, ledger.get24HourSum(1, 60_000));
-        ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25));
+        ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25, 0));
         assertEquals(30, ledger.getCurrentBalance());
         assertEquals(30, ledger.get24HourSum(1, 60_000));
-        ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10));
+        ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5));
         assertEquals(20, ledger.getCurrentBalance());
         assertEquals(20, ledger.get24HourSum(1, 60_000));
     }
@@ -68,13 +68,13 @@
     @Test
     public void test24HourSum() {
         final Ledger ledger = new Ledger();
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500));
+        ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0));
         assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
         ledger.recordTransaction(
-                new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500));
+                new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0));
         assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
         ledger.recordTransaction(
-                new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1));
+                new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1, 0));
         assertEquals(3001, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
         assertEquals(2501, ledger.get24HourSum(1, 25 * HOUR_IN_MILLIS));
         assertEquals(2501, ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS));
@@ -93,17 +93,17 @@
 
         final long now = getCurrentTimeMillis();
         Ledger.Transaction transaction1 = new Ledger.Transaction(
-                now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
+                now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800, 0);
         Ledger.Transaction transaction2 = new Ledger.Transaction(
-                now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
+                now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600, 0);
         Ledger.Transaction transaction3 = new Ledger.Transaction(
-                now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
+                now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600, 0);
         // Instant event
         Ledger.Transaction transaction4 = new Ledger.Transaction(
-                now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
+                now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500, 0);
         // Recent event
         Ledger.Transaction transaction5 = new Ledger.Transaction(
-                now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
+                now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400, 0);
         ledger.recordTransaction(transaction1);
         ledger.recordTransaction(transaction2);
         ledger.recordTransaction(transaction3);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index e1a4989..01e306e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -61,8 +61,6 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.internal.app.IBatteryStats;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -106,8 +104,6 @@
     @Mock
     private IBinder mVibrationToken;
     @Mock
-    private IBatteryStats mIBatteryStatsMock;
-    @Mock
     private VibrationConfig mVibrationConfigMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -178,8 +174,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -197,8 +193,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -219,8 +215,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -252,8 +248,8 @@
         thread.cancel();
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
@@ -404,8 +400,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -427,8 +423,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibration);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -446,8 +442,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -466,8 +462,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -486,8 +482,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+        verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
         assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty());
@@ -536,8 +532,8 @@
         waitForCompletion(thread);
 
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -573,8 +569,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
@@ -666,8 +662,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
@@ -690,8 +686,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -728,8 +724,8 @@
         VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -777,13 +773,13 @@
         controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
 
-        InOrder batterVerifier = inOrder(mIBatteryStatsMock);
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L));
-        batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        InOrder batteryVerifier = inOrder(mManagerHooks);
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
+        batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID));
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(thread.getVibrators().get(1).isVibrating());
@@ -952,8 +948,8 @@
 
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(80L));
-        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId));
         verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId));
@@ -1300,7 +1296,7 @@
 
     private VibrationThread startThreadAndDispatcher(Vibration vib) {
         VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
-                createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks);
+                createVibratorControllers(), mWakeLock, mManagerHooks);
         doAnswer(answer -> {
             thread.vibratorComplete(answer.getArgument(0));
             return null;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 52975ef..19111e5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -71,6 +71,7 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -78,6 +79,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
@@ -144,6 +146,8 @@
     private AppOpsManager mAppOpsManagerMock;
     @Mock
     private IInputManager mIInputManagerMock;
+    @Mock
+    private IBatteryStats mBatteryStatsMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -152,12 +156,14 @@
     private FakeVibrator mVibrator;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
     private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
+    private VibrationConfig mVibrationConfig;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
         InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
+        mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
 
         ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
         when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -222,6 +228,11 @@
                     }
 
                     @Override
+                    IBatteryStats getBatteryStatsService() {
+                        return mBatteryStatsMock;
+                    }
+
+                    @Override
                     VibratorController createVibratorController(int vibratorId,
                             VibratorController.OnVibrationCompleteListener listener) {
                         return mVibratorProviders.get(vibratorId)
@@ -382,6 +393,11 @@
         inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
         inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
         inOrderVerifier.verifyNoMoreInteractions();
+
+        InOrder batteryVerifier = inOrder(mBatteryStatsMock);
+        batteryVerifier.verify(mBatteryStatsMock)
+                .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs());
+        batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
     }
 
     @Test
@@ -731,6 +747,12 @@
         // Wait before checking it never played a second effect.
         assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1,
                 service, /* timeout= */ 50));
+
+        // The time estimate is recorded when the vibration starts, repeating vibrations
+        // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000).
+        verify(mBatteryStatsMock).noteVibratorOn(UID, 5000);
+        // The second vibration shouldn't have recorded that the vibrators were turned on.
+        verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
     }
 
     @Test
@@ -765,6 +787,9 @@
         when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+        // Mock alarm intensity equals to default value to avoid scaling in this test.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
         VibratorManagerService service = createSystemReadyService();
 
         CombinedVibration effect = CombinedVibration.createParallel(
@@ -804,6 +829,9 @@
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        // Mock alarm intensity equals to default value to avoid scaling in this test.
+        setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
         VibratorManagerService service = createSystemReadyService();
         // The native callback will be dispatched manually in this test.
         mTestLooper.stopAutoDispatchAndIgnoreExceptions();
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index 95448c7..d91134e 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -23,12 +23,15 @@
 import android.os.Parcelable;
 import android.telephony.Annotation.NetworkType;
 
+import com.android.telephony.Rlog;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Objects;
 
 public final class PhysicalChannelConfig implements Parcelable {
+    static final String TAG = "PhysicalChannelConfig";
 
     // TODO(b/72993578) consolidate these enums in a central location.
     /** @hide */
@@ -568,19 +571,21 @@
 
         public @NonNull Builder setNetworkType(@NetworkType int networkType) {
             if (!TelephonyManager.isNetworkTypeValid(networkType)) {
-                throw new IllegalArgumentException("Network type: " + networkType + " is invalid.");
+                Rlog.e(TAG, "Builder.setNetworkType: Network type " + networkType + " is invalid.");
+            } else {
+                mNetworkType = networkType;
             }
-            mNetworkType = networkType;
             return this;
         }
 
         public @NonNull Builder setFrequencyRange(int frequencyRange) {
             if (!ServiceState.isFrequencyRangeValid(frequencyRange)
                     && frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
-                throw new IllegalArgumentException("Frequency range: " + frequencyRange +
-                        " is invalid.");
+                Rlog.e(TAG, "Builder.setFrequencyRange: Frequency range " + frequencyRange
+                        + " is invalid.");
+            } else {
+                mFrequencyRange = frequencyRange;
             }
-            mFrequencyRange = frequencyRange;
             return this;
         }
 
@@ -596,19 +601,21 @@
 
         public @NonNull Builder setCellBandwidthDownlinkKhz(int cellBandwidthDownlinkKhz) {
             if (cellBandwidthDownlinkKhz < CELL_BANDWIDTH_UNKNOWN) {
-                throw new IllegalArgumentException("Cell downlink bandwidth(kHz): " +
-                        cellBandwidthDownlinkKhz + " is invalid.");
+                Rlog.e(TAG, "Builder.setCellBandwidthDownlinkKhz: Cell downlink bandwidth(kHz) "
+                        + cellBandwidthDownlinkKhz + " is invalid.");
+            } else {
+                mCellBandwidthDownlinkKhz = cellBandwidthDownlinkKhz;
             }
-            mCellBandwidthDownlinkKhz = cellBandwidthDownlinkKhz;
             return this;
         }
 
         public @NonNull Builder setCellBandwidthUplinkKhz(int cellBandwidthUplinkKhz) {
             if (cellBandwidthUplinkKhz < CELL_BANDWIDTH_UNKNOWN) {
-                throw new IllegalArgumentException("Cell uplink bandwidth(kHz): "+
-                        cellBandwidthUplinkKhz +" is invalid.");
+                Rlog.e(TAG, "Builder.setCellBandwidthUplinkKhz: Cell uplink bandwidth(kHz) "
+                        + cellBandwidthUplinkKhz + " is invalid.");
+            } else {
+                mCellBandwidthUplinkKhz = cellBandwidthUplinkKhz;
             }
-            mCellBandwidthUplinkKhz = cellBandwidthUplinkKhz;
             return this;
         }
 
@@ -625,19 +632,20 @@
 
         public @NonNull Builder setPhysicalCellId(int physicalCellId) {
             if (physicalCellId > PHYSICAL_CELL_ID_MAXIMUM_VALUE) {
-                throw new IllegalArgumentException("Physical cell Id: " + physicalCellId +
-                        " is over limit.");
+                Rlog.e(TAG, "Builder.setPhysicalCellId: Physical cell ID " + physicalCellId
+                        + " is over limit.");
+            } else {
+                mPhysicalCellId = physicalCellId;
             }
-            mPhysicalCellId = physicalCellId;
             return this;
         }
 
         public @NonNull Builder setBand(int band) {
             if (band <= BAND_UNKNOWN) {
-                throw new IllegalArgumentException("Band: " + band +
-                        " is invalid.");
+                Rlog.e(TAG, "Builder.setBand: Band " + band + " is invalid.");
+            } else {
+                mBand = band;
             }
-            mBand = band;
             return this;
         }
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f57c32c..0394a54 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4189,7 +4189,8 @@
      * {@link UiccSlotMapping} which consist of both physical slot index and port index.
      * Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot.
      * Port index is the index (enumerated value) for the associated port available on the SIM.
-     * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported.
+     * Each physical slot can have multiple ports if
+     * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} is supported.
      *
      * Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot
      * has one port:
@@ -4285,11 +4286,11 @@
     /**
      * Get the mapping from logical slots to physical sim slots and port indexes. Initially the
      * logical slot index was mapped to physical slot index, but with support for multi-enabled
-     * profile(MEP) logical slot is now mapped to port index.
+     * profile(MEP){@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},logical slot is now mapped to
+     * port index.
      *
      * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
      *         slots to ports and physical slots.
-     *
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 30ca162..3843a62 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.PackageManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -147,9 +148,10 @@
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
      *
-     * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one
-     * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
-     * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}
+     * @deprecated with support for MEP(multiple enabled profile)
+     * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, a SIM card can have more than one
+     * ICCID active at the same time. Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID.
+     * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}.
      *
      * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond.
      */
@@ -192,11 +194,11 @@
     }
 
     /*
-     * Whether the UICC card supports multiple enable profile(MEP)
+     * Whether the UICC card supports multiple enabled profile(MEP)
      * UICCs are generally MEP disabled, there can be only one active profile on the physical
      * sim card.
      *
-     * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP).
+     * @return {@code true} if the UICC is supporting multiple enabled profile(MEP).
      */
     public boolean isMultipleEnabledProfilesSupported() {
         return mIsMultipleEnabledProfilesSupported;
@@ -205,6 +207,9 @@
     /**
      * Get information regarding port, ICCID and its active status.
      *
+     * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+     * more than one {@link UiccPortInfo} object if the card is eUICC.
+     *
      * @return Collection of {@link UiccPortInfo}
      */
     public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java
index d1838c0..6fb0470 100644
--- a/telephony/java/android/telephony/UiccPortInfo.java
+++ b/telephony/java/android/telephony/UiccPortInfo.java
@@ -29,7 +29,9 @@
  * Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on
  * a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1.
  * Each port index is unique within an UICC, but not necessarily unique across UICC’s.
- * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0.
+ * For UICC's does not support MEP(Multi-enabled profile)
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, just return the default
+ * port index 0.
  */
 public final class UiccPortInfo implements Parcelable{
     private final String mIccId;
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 17f34db..17ce450 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -225,6 +226,9 @@
     /**
      * Get Information regarding port, iccid and its active status.
      *
+     * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return
+     * more than one {@link UiccPortInfo} object if the card is eUICC.
+     *
      * @return Collection of {@link UiccPortInfo}
      */
     public @NonNull Collection<UiccPortInfo> getPorts() {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index b6ae530..4820d33 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -37,6 +37,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
 import android.telephony.euicc.EuiccCardManager.ResetOption;
 import android.util.Log;
 
@@ -931,6 +932,21 @@
      * intent to prompt the user to accept the download. The caller should also be authorized to
      * manage the subscription to be downloaded.
      *
+     * <p>If device support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} and
+     * switchAfterDownload is {@code true}, the subscription will be enabled on an esim port based
+     * on the following selection rules:
+     * <ul>
+     *    <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then download
+     *    and enable the subscription on this port.
+     *    <li>In SS mode, if the embedded slot is not active, then try to download and enable the
+     *    subscription on the default port 0 of eUICC.
+     *    <li>In DSDS mode, find first available port to download and enable the subscription.
+     *    (see {@link #isSimPortAvailable(int)})
+     *</ul>
+     * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+     * will be returned in the callback intent to prompt the user to disable an already-active
+     * subscription.
+     *
      * @param subscription the subscription to download.
      * @param switchAfterDownload if true, the profile will be activated upon successful download.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
@@ -1141,14 +1157,25 @@
      * intent to prompt the user to accept the download. The caller should also be authorized to
      * manage the subscription to be enabled.
      *
-     * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription
-     * can be installed on different port from the eUICC. Calling apps with carrier privilege
-     * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions
-     * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to
-     * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC
-     * and the platform will internally resolve a port. If there is no available port,
-     * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback
-     * intent to prompt the user to disable an already-active subscription.
+     * <p> From Android T, devices might support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},
+     * the subscription can be installed on different port from the eUICC. Calling apps with
+     * carrier privilege (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently
+     * active subscriptions can use {@link #switchToSubscription(int, int, PendingIntent)} to
+     * specify which port to enable the subscription. Otherwise, use this API to enable the
+     * subscription on the eUICC and the platform will internally resolve a port based on following
+     * rules:
+     * <ul>
+     *    <li>always use the default port 0 is eUICC does not support MEP.
+     *    <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then enable
+     *    the subscription on this port.
+     *    <li>In SS mode, if the embedded slot is not active, then try to enable the subscription on
+     *    the default port 0 of eUICC.
+     *    <li>In DSDS mode, find first available port to enable the subscription.
+     *    (see {@link #isSimPortAvailable(int)})
+     *</ul>
+     * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}
+     * will be returned in the callback intent to prompt the user to disable an already-active
+     * subscription.
      *
      * @param subscriptionId the ID of the subscription to enable. May be
      *     {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
@@ -1197,7 +1224,15 @@
      *
      * <p> If the caller is passing invalid port index,
      * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code
-     * {@link #ERROR_INVALID_PORT} will be returned.
+     * {@link #ERROR_INVALID_PORT} will be returned. The port index is invalid if one of the
+     * following requirements is met:
+     * <ul>
+     *     <li>index is beyond the range of {@link UiccCardInfo#getPorts()}.
+     *     <li>In SS(Single SIM) mode, the embedded slot already has an active port with different
+     *     port index.
+     *     <li>In DSDS mode, if the psim slot is active and the embedded slot already has an active
+     *     empty port with different port index.
+     * </ul>
      *
      * <p> Depending on the target port and permission check,
      * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback
@@ -1522,8 +1557,8 @@
 
     /**
      * Returns whether the passing portIndex is available.
-     * A port is available if it has no profiles enabled on it or calling app has carrier privilege
-     * over the profile installed on the selected port.
+     * A port is available if it is active without enabled profile on it or
+     * calling app has carrier privilege over the profile installed on the selected port.
      * Always returns false if the cardId is a physical card.
      *
      * @param portIndex is an enumeration of the ports available on the UICC.