Merge "Fix a bug in UserSystemPackageInstaller#installWhitelistedSystemPackages"
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 d401373..aeb6abc 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -306,6 +306,10 @@
         return eventId & MASK_TYPE;
     }
 
+    static boolean isReward(int eventId) {
+        return getEventType(eventId) == TYPE_REWARD;
+    }
+
     @NonNull
     static String eventToString(int eventId) {
         switch (eventId & MASK_TYPE) {
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 2e2a9b5..3b5e444 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -22,10 +22,14 @@
 import static com.android.server.tare.TareUtils.dumpTime;
 import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.IndentingPrintWriter;
 import android.util.SparseLongArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -34,6 +38,21 @@
  * Ledger to track the last recorded balance and recent activities of an app.
  */
 class Ledger {
+    /** The window size within which rewards will be counted and used towards reward limiting. */
+    private static final long TOTAL_REWARD_WINDOW_MS = 24 * HOUR_IN_MILLIS;
+    /** The number of buckets to split {@link #TOTAL_REWARD_WINDOW_MS} into. */
+    @VisibleForTesting
+    static final int NUM_REWARD_BUCKET_WINDOWS = 4;
+    /**
+     * The duration size of each bucket resulting from splitting {@link #TOTAL_REWARD_WINDOW_MS}
+     * into smaller buckets.
+     */
+    private static final long REWARD_BUCKET_WINDOW_SIZE_MS =
+            TOTAL_REWARD_WINDOW_MS / NUM_REWARD_BUCKET_WINDOWS;
+    /** The maximum number of transactions to retain in memory at any one time. */
+    @VisibleForTesting
+    static final int MAX_TRANSACTION_COUNT = 50;
+
     static class Transaction {
         public final long startTimeMs;
         public final long endTimeMs;
@@ -54,18 +73,47 @@
         }
     }
 
+    static class RewardBucket {
+        @CurrentTimeMillisLong
+        public long startTimeMs;
+        public final SparseLongArray cumulativeDelta = new SparseLongArray();
+
+        private void reset() {
+            startTimeMs = 0;
+            cumulativeDelta.clear();
+        }
+    }
+
     /** Last saved balance. This doesn't take currently ongoing events into account. */
     private long mCurrentBalance = 0;
-    private final List<Transaction> mTransactions = new ArrayList<>();
-    private final SparseLongArray mCumulativeDeltaPerReason = new SparseLongArray();
-    private long mEarliestSumTime;
+    private final Transaction[] mTransactions = new Transaction[MAX_TRANSACTION_COUNT];
+    /** Index within {@link #mTransactions} where the next transaction should be placed. */
+    private int mTransactionIndex = 0;
+    private final RewardBucket[] mRewardBuckets = new RewardBucket[NUM_REWARD_BUCKET_WINDOWS];
+    /** Index within {@link #mRewardBuckets} of the current active bucket. */
+    private int mRewardBucketIndex = 0;
 
     Ledger() {
     }
 
-    Ledger(long currentBalance, @NonNull List<Transaction> transactions) {
+    Ledger(long currentBalance, @NonNull List<Transaction> transactions,
+            @NonNull List<RewardBucket> rewardBuckets) {
         mCurrentBalance = currentBalance;
-        mTransactions.addAll(transactions);
+
+        final int numTxs = transactions.size();
+        for (int i = Math.max(0, numTxs - MAX_TRANSACTION_COUNT); i < numTxs; ++i) {
+            mTransactions[mTransactionIndex++] = transactions.get(i);
+        }
+        mTransactionIndex %= MAX_TRANSACTION_COUNT;
+
+        final int numBuckets = rewardBuckets.size();
+        if (numBuckets > 0) {
+            // Set the index to -1 so that we put the first bucket in index 0.
+            mRewardBucketIndex = -1;
+            for (int i = Math.max(0, numBuckets - NUM_REWARD_BUCKET_WINDOWS); i < numBuckets; ++i) {
+                mRewardBuckets[++mRewardBucketIndex] = rewardBuckets.get(i);
+            }
+        }
     }
 
     long getCurrentBalance() {
@@ -74,66 +122,142 @@
 
     @Nullable
     Transaction getEarliestTransaction() {
-        if (mTransactions.size() > 0) {
-            return mTransactions.get(0);
+        for (int t = 0; t < mTransactions.length; ++t) {
+            final Transaction transaction =
+                    mTransactions[(mTransactionIndex + t) % mTransactions.length];
+            if (transaction != null) {
+                return transaction;
+            }
         }
         return null;
     }
 
     @NonNull
+    List<RewardBucket> getRewardBuckets() {
+        final long cutoffMs = getCurrentTimeMillis() - TOTAL_REWARD_WINDOW_MS;
+        final List<RewardBucket> list = new ArrayList<>(NUM_REWARD_BUCKET_WINDOWS);
+        for (int i = 1; i <= NUM_REWARD_BUCKET_WINDOWS; ++i) {
+            final int idx = (mRewardBucketIndex + i) % NUM_REWARD_BUCKET_WINDOWS;
+            final RewardBucket rewardBucket = mRewardBuckets[idx];
+            if (rewardBucket != null) {
+                if (cutoffMs <= rewardBucket.startTimeMs) {
+                    list.add(rewardBucket);
+                } else {
+                    rewardBucket.reset();
+                }
+            }
+        }
+        return list;
+    }
+
+    @NonNull
     List<Transaction> getTransactions() {
-        return mTransactions;
+        final List<Transaction> list = new ArrayList<>(MAX_TRANSACTION_COUNT);
+        for (int i = 0; i < MAX_TRANSACTION_COUNT; ++i) {
+            final int idx = (mTransactionIndex + i) % MAX_TRANSACTION_COUNT;
+            final Transaction transaction = mTransactions[idx];
+            if (transaction != null) {
+                list.add(transaction);
+            }
+        }
+        return list;
     }
 
     void recordTransaction(@NonNull Transaction transaction) {
-        mTransactions.add(transaction);
+        mTransactions[mTransactionIndex] = transaction;
         mCurrentBalance += transaction.delta;
+        mTransactionIndex = (mTransactionIndex + 1) % MAX_TRANSACTION_COUNT;
 
-        final long sum = mCumulativeDeltaPerReason.get(transaction.eventId);
-        mCumulativeDeltaPerReason.put(transaction.eventId, sum + transaction.delta);
-        mEarliestSumTime = Math.min(mEarliestSumTime, transaction.startTimeMs);
+        if (EconomicPolicy.isReward(transaction.eventId)) {
+            final RewardBucket bucket = getCurrentRewardBucket();
+            bucket.cumulativeDelta.put(transaction.eventId,
+                    bucket.cumulativeDelta.get(transaction.eventId, 0) + transaction.delta);
+        }
+    }
+
+    @NonNull
+    private RewardBucket getCurrentRewardBucket() {
+        RewardBucket bucket = mRewardBuckets[mRewardBucketIndex];
+        final long now = getCurrentTimeMillis();
+        if (bucket == null) {
+            bucket = new RewardBucket();
+            bucket.startTimeMs = now;
+            mRewardBuckets[mRewardBucketIndex] = bucket;
+            return bucket;
+        }
+
+        if (now - bucket.startTimeMs < REWARD_BUCKET_WINDOW_SIZE_MS) {
+            return bucket;
+        }
+
+        mRewardBucketIndex = (mRewardBucketIndex + 1) % NUM_REWARD_BUCKET_WINDOWS;
+        bucket = mRewardBuckets[mRewardBucketIndex];
+        if (bucket == null) {
+            bucket = new RewardBucket();
+            mRewardBuckets[mRewardBucketIndex] = bucket;
+        }
+        bucket.reset();
+        // Using now as the start time means there will be some gaps between sequential buckets,
+        // but makes processing of large gaps between events easier.
+        bucket.startTimeMs = now;
+        return bucket;
     }
 
     long get24HourSum(int eventId, final long now) {
         final long windowStartTime = now - 24 * HOUR_IN_MILLIS;
-        if (mEarliestSumTime < windowStartTime) {
-            // Need to redo sums
-            mCumulativeDeltaPerReason.clear();
-            for (int i = mTransactions.size() - 1; i >= 0; --i) {
-                final Transaction transaction = mTransactions.get(i);
-                if (transaction.endTimeMs <= windowStartTime) {
-                    break;
-                }
-                long sum = mCumulativeDeltaPerReason.get(transaction.eventId);
-                if (transaction.startTimeMs >= windowStartTime) {
-                    sum += transaction.delta;
-                } else {
-                    // Pro-rate durationed deltas. Intentionally floor the result.
-                    sum += (long) (1.0 * (transaction.endTimeMs - windowStartTime)
-                            * transaction.delta)
-                            / (transaction.endTimeMs - transaction.startTimeMs);
-                }
-                mCumulativeDeltaPerReason.put(transaction.eventId, sum);
+        long sum = 0;
+        for (int i = 0; i < mRewardBuckets.length; ++i) {
+            final RewardBucket bucket = mRewardBuckets[i];
+            if (bucket != null
+                    && bucket.startTimeMs >= windowStartTime && bucket.startTimeMs < now) {
+                sum += bucket.cumulativeDelta.get(eventId, 0);
             }
-            mEarliestSumTime = windowStartTime;
         }
-        return mCumulativeDeltaPerReason.get(eventId);
+        return sum;
     }
 
-    /** Deletes transactions that are older than {@code minAgeMs}. */
-    void removeOldTransactions(long minAgeMs) {
+    /**
+     * Deletes transactions that are older than {@code minAgeMs}.
+     * @return The earliest transaction in the ledger, or {@code null} if there are no more
+     * transactions.
+     */
+    @Nullable
+    Transaction removeOldTransactions(long minAgeMs) {
         final long cutoff = getCurrentTimeMillis() - minAgeMs;
-        while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) {
-            mTransactions.remove(0);
+        for (int t = 0; t < mTransactions.length; ++t) {
+            final int idx = (mTransactionIndex + t) % mTransactions.length;
+            final Transaction transaction = mTransactions[idx];
+            if (transaction == null) {
+                continue;
+            }
+            if (transaction.endTimeMs <= cutoff) {
+                mTransactions[idx] = null;
+            } else {
+                // Everything we look at after this transaction will also be within the window,
+                // so no need to go further.
+                return transaction;
+            }
         }
+        return null;
     }
 
     void dump(IndentingPrintWriter pw, int numRecentTransactions) {
         pw.print("Current balance", cakeToString(getCurrentBalance())).println();
+        pw.println();
 
-        final int size = mTransactions.size();
-        for (int i = Math.max(0, size - numRecentTransactions); i < size; ++i) {
-            final Transaction transaction = mTransactions.get(i);
+        boolean printedTransactionTitle = false;
+        for (int t = 0; t < Math.min(MAX_TRANSACTION_COUNT, numRecentTransactions); ++t) {
+            final int idx = (mTransactionIndex - t + MAX_TRANSACTION_COUNT) % MAX_TRANSACTION_COUNT;
+            final Transaction transaction = mTransactions[idx];
+            if (transaction == null) {
+                continue;
+            }
+
+            if (!printedTransactionTitle) {
+                pw.println("Transactions:");
+                pw.increaseIndent();
+                printedTransactionTitle = true;
+            }
 
             dumpTime(pw, transaction.startTimeMs);
             pw.print("--");
@@ -151,5 +275,42 @@
             pw.print(cakeToString(transaction.ctp));
             pw.println(")");
         }
+        if (printedTransactionTitle) {
+            pw.decreaseIndent();
+            pw.println();
+        }
+
+        final long now = getCurrentTimeMillis();
+        boolean printedBucketTitle = false;
+        for (int b = 0; b < NUM_REWARD_BUCKET_WINDOWS; ++b) {
+            final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS)
+                    % NUM_REWARD_BUCKET_WINDOWS;
+            final RewardBucket rewardBucket = mRewardBuckets[idx];
+            if (rewardBucket == null) {
+                continue;
+            }
+
+            if (!printedBucketTitle) {
+                pw.println("Reward buckets:");
+                pw.increaseIndent();
+                printedBucketTitle = true;
+            }
+
+            dumpTime(pw, rewardBucket.startTimeMs);
+            pw.print(" (");
+            TimeUtils.formatDuration(now - rewardBucket.startTimeMs, pw);
+            pw.println(" ago):");
+            pw.increaseIndent();
+            for (int r = 0; r < rewardBucket.cumulativeDelta.size(); ++r) {
+                pw.print(EconomicPolicy.eventToString(rewardBucket.cumulativeDelta.keyAt(r)));
+                pw.print(": ");
+                pw.println(cakeToString(rewardBucket.cumulativeDelta.valueAt(r)));
+            }
+            pw.decreaseIndent();
+        }
+        if (printedBucketTitle) {
+            pw.decreaseIndent();
+            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 941cc39..8f7657e 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -62,15 +62,14 @@
     private static final int MAX_NUM_TRANSACTION_DUMP = 25;
     /**
      * The maximum amount of time we'll keep a transaction around for.
-     * For now, only keep transactions we actually have a use for. We can increase it if we want
-     * to use older transactions or provide older transactions to apps.
      */
-    private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS;
+    private static final long MAX_TRANSACTION_AGE_MS = 8 * 24 * HOUR_IN_MILLIS;
 
     private static final String XML_TAG_HIGH_LEVEL_STATE = "irs-state";
     private static final String XML_TAG_LEDGER = "ledger";
     private static final String XML_TAG_TARE = "tare";
     private static final String XML_TAG_TRANSACTION = "transaction";
+    private static final String XML_TAG_REWARD_BUCKET = "rewardBucket";
     private static final String XML_TAG_USER = "user";
     private static final String XML_TAG_PERIOD_REPORT = "report";
 
@@ -346,8 +345,8 @@
                 for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
                     final String pkgName = mLedgers.keyAt(uIdx, pIdx);
                     final Ledger ledger = mLedgers.get(userId, pkgName);
-                    ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
-                    Ledger.Transaction transaction = ledger.getEarliestTransaction();
+                    final Ledger.Transaction transaction =
+                            ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
                     if (transaction != null) {
                         earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
                     }
@@ -370,6 +369,7 @@
         final String pkgName;
         final long curBalance;
         final List<Ledger.Transaction> transactions = new ArrayList<>();
+        final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
 
         pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
         curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
@@ -391,8 +391,7 @@
                 }
                 continue;
             }
-            if (eventType != XmlPullParser.START_TAG || !XML_TAG_TRANSACTION.equals(tagName)) {
-                // Expecting only "transaction" tags.
+            if (eventType != XmlPullParser.START_TAG || tagName == null) {
                 Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
                 return null;
             }
@@ -402,25 +401,37 @@
             if (DEBUG) {
                 Slog.d(TAG, "Starting ledger tag: " + tagName);
             }
-            final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
-            final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
-            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;
+            switch (tagName) {
+                case XML_TAG_TRANSACTION:
+                    final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
+                    if (endTime <= endTimeCutoff) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Skipping event because it's too old.");
+                        }
+                        continue;
+                    }
+                    final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+                    final long startTime = parser.getAttributeLong(null, XML_ATTR_START_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);
+                    transactions.add(
+                            new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
+                    break;
+                case XML_TAG_REWARD_BUCKET:
+                    rewardBuckets.add(readRewardBucketFromXml(parser));
+                    break;
+                default:
+                    // Expecting only "transaction" and "rewardBucket" tags.
+                    Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+                    return null;
             }
-            transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
         }
 
         if (!isInstalled) {
             return null;
         }
-        return Pair.create(pkgName, new Ledger(curBalance, transactions));
+        return Pair.create(pkgName, new Ledger(curBalance, transactions, rewardBuckets));
     }
 
     /**
@@ -508,6 +519,44 @@
         return report;
     }
 
+    /**
+     * @param parser Xml parser at the beginning of a {@value #XML_TAG_REWARD_BUCKET} tag. The next
+     *               "parser.next()" call will take the parser into the body of the tag.
+     * @return Newly instantiated {@link Ledger.RewardBucket} holding all the information we just
+     * read out of the xml tag.
+     */
+    @Nullable
+    private static Ledger.RewardBucket readRewardBucketFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        final Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+
+        rewardBucket.startTimeMs = parser.getAttributeLong(null, XML_ATTR_START_TIME);
+
+        for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+                eventType = parser.next()) {
+            final String tagName = parser.getName();
+            if (eventType == XmlPullParser.END_TAG) {
+                if (XML_TAG_REWARD_BUCKET.equals(tagName)) {
+                    // We've reached the end of the rewardBucket tag.
+                    break;
+                }
+                continue;
+            }
+            if (eventType != XmlPullParser.START_TAG || !XML_ATTR_DELTA.equals(tagName)) {
+                // Expecting only delta tags.
+                Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+                return null;
+            }
+
+            final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
+            final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+            rewardBucket.cumulativeDelta.put(eventId, delta);
+        }
+
+        return rewardBucket;
+    }
+
     private void scheduleCleanup(long earliestEndTime) {
         if (earliestEndTime == Long.MAX_VALUE) {
             return;
@@ -595,6 +644,11 @@
                 }
                 writeTransaction(out, transaction);
             }
+
+            final List<Ledger.RewardBucket> rewardBuckets = ledger.getRewardBuckets();
+            for (int r = 0; r < rewardBuckets.size(); ++r) {
+                writeRewardBucket(out, rewardBuckets.get(r));
+            }
             out.endTag(null, XML_TAG_LEDGER);
         }
         out.endTag(null, XML_TAG_USER);
@@ -616,6 +670,23 @@
         out.endTag(null, XML_TAG_TRANSACTION);
     }
 
+    private static void writeRewardBucket(@NonNull TypedXmlSerializer out,
+            @NonNull Ledger.RewardBucket rewardBucket) throws IOException {
+        final int numEvents = rewardBucket.cumulativeDelta.size();
+        if (numEvents == 0) {
+            return;
+        }
+        out.startTag(null, XML_TAG_REWARD_BUCKET);
+        out.attributeLong(null, XML_ATTR_START_TIME, rewardBucket.startTimeMs);
+        for (int i = 0; i < numEvents; ++i) {
+            out.startTag(null, XML_ATTR_DELTA);
+            out.attributeInt(null, XML_ATTR_EVENT_ID, rewardBucket.cumulativeDelta.keyAt(i));
+            out.attributeLong(null, XML_ATTR_DELTA, rewardBucket.cumulativeDelta.valueAt(i));
+            out.endTag(null, XML_ATTR_DELTA);
+        }
+        out.endTag(null, XML_TAG_REWARD_BUCKET);
+    }
+
     private static void writeReport(@NonNull TypedXmlSerializer out,
             @NonNull Analyst.Report report) throws IOException {
         out.startTag(null, XML_TAG_PERIOD_REPORT);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 8db298f..861a850 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,8 +1325,11 @@
      * flashlight brightness level via
      * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
      * If this value is equal to 1, flashlight brightness control is not supported.
-     * The value for this key will be null for devices with no flash unit.
-     * This level must be set to a safe value to prevent any burn out issues.</p>
+     * The value for this key will be null for devices with no flash unit.</p>
+     * <p>The maximum value is guaranteed to be safe to use for an indefinite duration in
+     * terms of device flashlight lifespan, but may be too bright for comfort for many
+     * use cases. Use the default torch brightness value to avoid problems with an
+     * over-bright flashlight.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      */
     @PublicKey
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 91d6a9b..40f7533 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -117,6 +117,7 @@
     /**
      * Shuts down the service
      */
+    @EnforcePermission("SHUTDOWN")
     void shutdown();
 
     /**
@@ -277,6 +278,7 @@
      */
     void setUidOnMeteredNetworkDenylist(int uid, boolean enable);
     void setUidOnMeteredNetworkAllowlist(int uid, boolean enable);
+    @EnforcePermission("NETWORK_SETTINGS")
     boolean setDataSaverModeEnabled(boolean enable);
 
     void setUidCleartextNetworkPolicy(int uid, int policy);
@@ -308,5 +310,6 @@
     void removeInterfaceFromLocalNetwork(String iface);
     int removeRoutesFromLocalNetwork(in List<RouteInfo> routes);
 
+    @EnforcePermission("OBSERVE_NETWORK_POLICY")
     boolean isNetworkRestricted(int uid);
 }
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 5dae313..c692981 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -145,5 +145,8 @@
      *
      * @hide
      */
-    SurfaceSyncGroup.SyncTarget getSyncTarget();
+    @Nullable
+    default SurfaceSyncGroup.SyncTarget getSyncTarget() {
+        return null;
+    }
 }
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
index 8dbaec4..60edf35 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -139,11 +139,13 @@
             @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
             @Nullable IRemoteInputConnection remoteInputConnection,
             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            int unverifiedTargetSdkVersion, @UserIdInt int userId,
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
         try {
             return mTarget.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
                     startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
-                    remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, imeDispatcher);
+                    remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
+                    imeDispatcher);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index aacd4ec..8ce134e7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -285,9 +285,10 @@
     private static final String SUBTYPE_MODE_VOICE = "voice";
 
     /**
-     * Provide this to {@link IInputMethodManager#startInputOrWindowGainedFocus(
-     * int, IInputMethodClient, IBinder, int, int, int, EditorInfo,
-     * com.android.internal.inputmethod.IRemoteInputConnection, int)} to receive
+     * Provide this to {@link IInputMethodManagerInvoker#startInputOrWindowGainedFocus(int,
+     * IInputMethodClient, IBinder, int, int, int, EditorInfo,
+     * com.android.internal.inputmethod.IRemoteInputConnection, IRemoteAccessibilityInputConnection,
+     * int, int, ImeOnBackInvokedDispatcher)} to receive
      * {@link android.window.OnBackInvokedCallback} registrations from IME.
      */
     private final ImeOnBackInvokedDispatcher mImeDispatcher =
@@ -790,7 +791,7 @@
                         null,
                         null, null,
                         mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
-                        mImeDispatcher);
+                        UserHandle.myUserId(), mImeDispatcher);
             }
         }
 
@@ -2331,22 +2332,22 @@
         // Okay we are now ready to call into the served view and have it
         // do its stuff.
         // Life is good: let's hook everything up!
-        EditorInfo tba = new EditorInfo();
+        EditorInfo editorInfo = new EditorInfo();
         // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
         // system can verify the consistency between the uid of this process and package name passed
         // from here. See comment of Context#getOpPackageName() for details.
-        tba.packageName = view.getContext().getOpPackageName();
-        tba.autofillId = view.getAutofillId();
-        tba.fieldId = view.getId();
-        InputConnection ic = view.onCreateInputConnection(tba);
-        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+        editorInfo.packageName = view.getContext().getOpPackageName();
+        editorInfo.autofillId = view.getAutofillId();
+        editorInfo.fieldId = view.getId();
+        InputConnection ic = view.onCreateInputConnection(editorInfo);
+        if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
 
         // Clear autofill and field ids if a connection could not be established.
         // This ensures that even disconnected EditorInfos have well-defined attributes,
         // making them consistently and straightforwardly comparable.
         if (ic == null) {
-            tba.autofillId = AutofillId.NO_AUTOFILL_ID;
-            tba.fieldId = 0;
+            editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
+            editorInfo.fieldId = 0;
         }
 
         final Handler icHandler;
@@ -2378,7 +2379,7 @@
             }
 
             // Hook 'em up and let 'er rip.
-            mCurrentEditorInfo = tba.createCopyInternal();
+            mCurrentEditorInfo = editorInfo.createCopyInternal();
             // Store the previously served connection so that we can determine whether it is safe
             // to skip the call to startInputOrWindowGainedFocus in the IMMS
             final RemoteInputConnectionImpl previouslyServedConnection = mServedInputConnection;
@@ -2391,8 +2392,8 @@
             }
             final RemoteInputConnectionImpl servedInputConnection;
             if (ic != null) {
-                mCursorSelStart = tba.initialSelStart;
-                mCursorSelEnd = tba.initialSelEnd;
+                mCursorSelStart = editorInfo.initialSelStart;
+                mCursorSelEnd = editorInfo.initialSelEnd;
                 mInitialSelStart = mCursorSelStart;
                 mInitialSelEnd = mCursorSelEnd;
                 mCursorCandStart = -1;
@@ -2418,7 +2419,7 @@
 
             if (DEBUG) {
                 Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
-                        + ic + " tba=" + tba + " startInputFlags="
+                        + ic + " editorInfo=" + editorInfo + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
             }
 
@@ -2437,19 +2438,21 @@
                 }
                 return false;
             }
+            final int targetUserId = editorInfo.targetInputMethodUser != null
+                    ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
             res = mServiceInvoker.startInputOrWindowGainedFocus(
                     startInputReason, mClient, windowGainingFocus, startInputFlags,
-                    softInputMode, windowFlags, tba, servedInputConnection,
+                    softInputMode, windowFlags, editorInfo, servedInputConnection,
                     servedInputConnection == null ? null
                             : servedInputConnection.asIRemoteAccessibilityInputConnection(),
-                    view.getContext().getApplicationInfo().targetSdkVersion,
+                    view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                     mImeDispatcher);
             if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
             if (res == null) {
                 Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
                         + " null. startInputReason="
                         + InputMethodDebug.startInputReasonToString(startInputReason)
-                        + " editorInfo=" + tba
+                        + " editorInfo=" + editorInfo
                         + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
                 return false;
@@ -2491,9 +2494,9 @@
         if (ic != null && res != null && res.method != null) {
             if (DEBUG) {
                 Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
-                        + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler);
+                        + ", ic=" + ic + ", editorInfo=" + editorInfo + ", handler=" + icHandler);
             }
-            reportInputConnectionOpened(ic, tba, icHandler, view);
+            reportInputConnectionOpened(ic, editorInfo, icHandler, view);
         }
 
         return true;
@@ -2541,8 +2544,8 @@
     }
 
     private void reportInputConnectionOpened(
-            InputConnection ic, EditorInfo tba, Handler icHandler, View view) {
-        view.onInputConnectionOpenedInternal(ic, tba, icHandler);
+            InputConnection ic, EditorInfo editorInfo, Handler icHandler, View view) {
+        view.onInputConnectionOpenedInternal(ic, editorInfo, icHandler);
         final ViewRootImpl viewRoot = view.getViewRootImpl();
         if (viewRoot != null) {
             viewRoot.getHandwritingInitiator().onInputConnectionCreated(view);
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 5672697..4248096 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -186,7 +186,11 @@
         if (viewRoot == null) {
             return false;
         }
-        return addToSync(viewRoot.getSyncTarget());
+        SyncTarget syncTarget = viewRoot.getSyncTarget();
+        if (syncTarget == null) {
+            return false;
+        }
+        return addToSync(syncTarget);
     }
 
     /**
diff --git a/core/java/com/android/internal/app/procstats/IProcessStats.aidl b/core/java/com/android/internal/app/procstats/IProcessStats.aidl
index a2eca3a..84b2a35 100644
--- a/core/java/com/android/internal/app/procstats/IProcessStats.aidl
+++ b/core/java/com/android/internal/app/procstats/IProcessStats.aidl
@@ -21,7 +21,9 @@
 import com.android.internal.app.procstats.ProcessStats;
 
 interface IProcessStats {
+    @EnforcePermission("PACKAGE_USAGE_STATS")
     byte[] getCurrentStats(out List<ParcelFileDescriptor> historic);
+    @EnforcePermission("PACKAGE_USAGE_STATS")
     ParcelFileDescriptor getStatsOverTime(long minTime);
     int getCurrentMemoryState();
 
@@ -43,6 +45,7 @@
      * @param List of Files of individual commits in protobuf binary or one that is merged from them.
      * @param ProcessStats object that will be used to return the full set of merged stats.
      */
+     @EnforcePermission("PACKAGE_USAGE_STATS")
      long getCommittedStatsMerged(long highWaterMarkMs, int section, boolean doAggregate,
         out List<ParcelFileDescriptor> committedStats, out ProcessStats mergedStats);
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index cbfa0e2..eb8b860 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -72,7 +72,8 @@
             /* @android.view.WindowManager.LayoutParams.Flags */ int windowFlags,
             in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
             in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher);
+            int unverifiedTargetSdkVersion, int userId,
+            in ImeOnBackInvokedDispatcher imeDispatcher);
 
     void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d52c70b..ae1c9461 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -907,6 +907,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "-1237827119": {
+      "message": "Schedule remove starting %s startingWindow=%s animate=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1228653755": {
       "message": "Launch on display check: displayId=%d callingPid=%d callingUid=%d",
       "level": "DEBUG",
@@ -1015,12 +1021,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "-1128015008": {
-      "message": "Schedule remove starting %s startingWindow=%s startingView=%s Callers=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1117599386": {
       "message": "Deferring rotation, display is not enabled.",
       "level": "VERBOSE",
@@ -3139,6 +3139,12 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/LockTaskController.java"
     },
+    "956467125": {
+      "message": "Reparenting Activity to embedded TaskFragment, but the Activity is not collected",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "958338552": {
       "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
       "level": "VERBOSE",
@@ -3877,12 +3883,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "1742235936": {
-      "message": "Removing startingView=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1746778201": {
       "message": "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
       "level": "INFO",
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c14c3c5..5ab21bc 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -237,6 +237,7 @@
     private final boolean mUnlockedDeviceRequired;
     private final boolean mIsStrongBoxBacked;
     private final int mMaxUsageCount;
+    private final boolean mRollbackResistant;
 
     private KeyProtection(
             Date keyValidityStart,
@@ -259,7 +260,8 @@
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
             boolean isStrongBoxBacked,
-            int maxUsageCount) {
+            int maxUsageCount,
+            boolean rollbackResistant) {
         mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
         mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
         mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -283,6 +285,7 @@
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mIsStrongBoxBacked = isStrongBoxBacked;
         mMaxUsageCount = maxUsageCount;
+        mRollbackResistant = rollbackResistant;
     }
 
     /**
@@ -563,6 +566,17 @@
     }
 
     /**
+     * Returns {@code true} if the key is rollback-resistant, meaning that when deleted it is
+     * guaranteed to be permanently deleted and unusable.
+     *
+     * @see Builder#setRollbackResistant(boolean)
+     * @hide
+     */
+    public boolean isRollbackResistant() {
+        return mRollbackResistant;
+    }
+
+    /**
      * Builder of {@link KeyProtection} instances.
      */
     public final static class Builder {
@@ -591,6 +605,7 @@
         private boolean mIsStrongBoxBacked = false;
         private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
         private String mAttestKeyAlias = null;
+        private boolean mRollbackResistant = false;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -1097,6 +1112,21 @@
         }
 
         /**
+         * Sets whether the key should be rollback-resistant, meaning that when deleted it is
+         * guaranteed to be permanently deleted and unusable.  Not all implementations support
+         * rollback-resistant keys.  This method is hidden because implementations only support a
+         * limited number of rollback-resistant keys; currently the available space is reserved for
+         * critical system keys.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setRollbackResistant(boolean rollbackResistant) {
+            mRollbackResistant = rollbackResistant;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@link KeyProtection}.
          *
          * @throws IllegalArgumentException if a required field is missing
@@ -1124,7 +1154,8 @@
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
                     mIsStrongBoxBacked,
-                    mMaxUsageCount);
+                    mMaxUsageCount,
+                    mRollbackResistant);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 33411e1..dfda356 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -783,6 +783,12 @@
                         params.getMaxUsageCount()
                 ));
             }
+
+            if (params.isRollbackResistant()) {
+                importArgs.add(KeyStore2ParameterUtils.makeBool(
+                        KeymasterDefs.KM_TAG_ROLLBACK_RESISTANT
+                ));
+            }
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f85f9d6..5dd5149 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -411,8 +411,8 @@
 
     @WMSingleton
     @Provides
-    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper() {
-        return new PipSurfaceTransactionHelper();
+    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+        return new PipSurfaceTransactionHelper(context);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index c0bc108..3ac08a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -39,6 +39,10 @@
     private int mCornerRadius;
     private int mShadowRadius;
 
+    public PipSurfaceTransactionHelper(Context context) {
+        onDensityOrFontScaleChanged(context);
+    }
+
     /**
      * Called when display size or font size of settings changed
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 913e4b7..1155ea1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -445,7 +445,7 @@
             // When exit to fullscreen with Shell transition enabled, we update the Task windowing
             // mode directly so that it can also trigger display rotation and visibility update in
             // the same transition if there will be any.
-            wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            wct.setWindowingMode(mToken, getOutPipWindowingMode());
             // We can inherit the parent bounds as it is going to be fullscreen. The
             // destinationBounds calculated above will be incorrect if this is with rotation.
             wct.setBounds(mToken, null);
@@ -544,7 +544,7 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.setBounds(mToken, null);
-            wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+            wct.setWindowingMode(mToken, getOutPipWindowingMode());
             wct.reorder(mToken, false);
             mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
                     null /* destinationBounds */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 53ec39d..7fb961f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -352,41 +352,31 @@
 
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
+        if (fillInIntent == null) {
+            fillInIntent = new Intent();
+        }
+        // Flag this as a no-user-action launch to prevent sending user leaving event to the
+        // current top activity since it's going to be put into another side of the split. This
+        // prevents the current top activity from going into pip mode due to user leaving event.
+        fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+
+        // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
+        // split.
+        if (isLaunchingAdjacently(intent.getIntent(), position)) {
+            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+        }
+
         if (!ENABLE_SHELL_TRANSITIONS) {
             startIntentLegacy(intent, fillInIntent, position, options);
             return;
         }
 
-        try {
-            options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
-                    null /* wct */);
-
-            if (fillInIntent == null) {
-                fillInIntent = new Intent();
-            }
-            // Flag this as a no-user-action launch to prevent sending user leaving event to the
-            // current top activity since it's going to be put into another side of the split. This
-            // prevents the current top activity from going into pip mode due to user leaving event.
-            fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
-
-            // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
-            // split.
-            if (isLaunchingAdjacently(intent.getIntent(), position)) {
-                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
-            }
-
-            intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
-                    null /* requiredPermission */, options);
-        } catch (PendingIntent.CanceledException e) {
-            Slog.e(TAG, "Failed to launch task", e);
-        }
+        mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
-    private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent,
+    private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
-        boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position);
-
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         mStageCoordinator.prepareEvictChildTasks(position, evictWct);
 
@@ -397,8 +387,8 @@
                     IRemoteAnimationFinishedCallback finishedCallback,
                     SurfaceControl.Transaction t) {
                 if (apps == null || apps.length == 0) {
-                    if (startSameActivityAdjacently) {
-                        // Switch split position if dragging the same activity to another side.
+                    // Switch the split position if launching as MULTIPLE_TASK failed.
+                    if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
                         setSideStagePosition(SplitLayout.reversePosition(
                                 mStageCoordinator.getSideStagePosition()));
                     }
@@ -408,8 +398,6 @@
                     return;
                 }
 
-                mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
-                        false /* applyResizingOffset */);
                 for (int i = 0; i < apps.length; ++i) {
                     if (apps[i].mode == MODE_OPENING) {
                         t.show(apps[i].leash);
@@ -432,18 +420,6 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
 
-        // Flag this as a no-user-action launch to prevent sending user leaving event to the current
-        // top activity since it's going to be put into another side of the split. This prevents the
-        // current top activity from going into pip mode due to user leaving event.
-        if (fillInIntent == null) {
-            fillInIntent = new Intent();
-        }
-        fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
-        if (startSameActivityAdjacently) {
-            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
-        }
-
         wct.sendPendingIntent(intent, fillInIntent, options);
         mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 056cd58..83bdf8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -62,13 +62,12 @@
     private final Runnable mOnFinish;
 
     DismissTransition mPendingDismiss = null;
-    IBinder mPendingEnter = null;
-    IBinder mPendingRecent = null;
+    TransitSession mPendingEnter = null;
+    TransitSession mPendingRecent = null;
 
     private IBinder mAnimatingTransition = null;
     OneShotRemoteHandler mPendingRemoteHandler = null;
     private OneShotRemoteHandler mActiveRemoteHandler = null;
-    private boolean mEnterTransitionMerged;
 
     private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
 
@@ -145,7 +144,7 @@
                 continue;
             }
 
-            if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+            if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
                     || sideRoot.equals(change.getContainer()))) {
                 t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
                 t.setWindowCrop(leash, change.getEndAbsBounds().width(),
@@ -171,12 +170,40 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    boolean isPendingTransition(IBinder transition) {
+        return isPendingEnter(transition)
+                || isPendingDismiss(transition)
+                || isPendingRecent(transition);
+    }
+
+    boolean isPendingEnter(IBinder transition) {
+        return mPendingEnter != null && mPendingEnter.mTransition == transition;
+    }
+
+    boolean isPendingRecent(IBinder transition) {
+        return mPendingRecent != null && mPendingRecent.mTransition == transition;
+    }
+
+    boolean isPendingDismiss(IBinder transition) {
+        return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
+    }
+
     /** Starts a transition to enter split with a remote transition animator. */
-    IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
-            @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
-            @NonNull Transitions.TransitionHandler handler) {
+    IBinder startEnterTransition(
+            @WindowManager.TransitionType int transitType,
+            WindowContainerTransaction wct,
+            @Nullable RemoteTransition remoteTransition,
+            Transitions.TransitionHandler handler,
+            @Nullable TransitionCallback callback) {
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
-        mPendingEnter = transition;
+        setEnterTransition(transition, remoteTransition, callback);
+        return transition;
+    }
+
+    /** Sets a transition to enter split. */
+    void setEnterTransition(@NonNull IBinder transition,
+            @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+        mPendingEnter = new TransitSession(transition, callback);
 
         if (remoteTransition != null) {
             // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -184,7 +211,9 @@
                     mTransitions.getMainExecutor(), remoteTransition);
             mPendingRemoteHandler.setTransition(transition);
         }
-        return transition;
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced Enter split screen");
     }
 
     /** Starts a transition to dismiss split. */
@@ -209,8 +238,8 @@
     }
 
     void setRecentTransition(@NonNull IBinder transition,
-            @Nullable RemoteTransition remoteTransition) {
-        mPendingRecent = transition;
+            @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+        mPendingRecent = new TransitSession(transition, callback);
 
         if (remoteTransition != null) {
             // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -226,6 +255,18 @@
     void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
             IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
         if (mergeTarget != mAnimatingTransition) return;
+
+        if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
+            mPendingRecent.mCallback = new TransitionCallback() {
+                @Override
+                public void onTransitionFinished(WindowContainerTransaction finishWct,
+                        SurfaceControl.Transaction finishT) {
+                    // Since there's an entering transition merged, recent transition no longer
+                    // need to handle entering split screen after the transition finished.
+                }
+            };
+        }
+
         if (mActiveRemoteHandler != null) {
             mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
         } else {
@@ -247,38 +288,55 @@
     }
 
     void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
-        if (aborted) return;
+        if (isPendingEnter(transition)) {
+            if (!aborted) {
+                // An enter transition got merged, appends the rest operations to finish entering
+                // split screen.
+                // TODO (b/238856352): Passed-in the proper finish transition to merge instead.
+                if (mFinishTransaction == null) {
+                    mFinishTransaction = mTransactionPool.acquire();
+                }
+                mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+            }
 
-        // Once a pending enter transition got merged, make sure to append the reset of finishing
-        // operations to the finish transition.
-        if (transition == mPendingEnter) {
-            mFinishTransaction = mTransactionPool.acquire();
-            mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+            mPendingEnter.mCallback.onTransitionConsumed(aborted);
             mPendingEnter = null;
             mPendingRemoteHandler = null;
-            mEnterTransitionMerged = true;
+        } else if (isPendingDismiss(transition)) {
+            mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+            mPendingDismiss = null;
+        } else if (isPendingRecent(transition)) {
+            mPendingRecent.mCallback.onTransitionConsumed(aborted);
+            mPendingRecent = null;
+            mPendingRemoteHandler = null;
         }
     }
 
     void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
         if (!mAnimations.isEmpty()) return;
-        if (mAnimatingTransition == mPendingEnter) {
+
+        TransitionCallback callback = null;
+        if (isPendingEnter(mAnimatingTransition)) {
+            callback = mPendingEnter.mCallback;
             mPendingEnter = null;
         }
-        if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
+        if (isPendingDismiss(mAnimatingTransition)) {
+            callback = mPendingDismiss.mCallback;
             mPendingDismiss = null;
         }
-        if (mAnimatingTransition == mPendingRecent) {
-            if (!mEnterTransitionMerged) {
-                if (wct == null) wct = new WindowContainerTransaction();
-                mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction);
-            }
+        if (isPendingRecent(mAnimatingTransition)) {
+            callback = mPendingRecent.mCallback;
             mPendingRecent = null;
         }
+
+        if (callback != null) {
+            if (wct == null) wct = new WindowContainerTransaction();
+            callback.onTransitionFinished(wct, mFinishTransaction);
+        }
+
         mPendingRemoteHandler = null;
         mActiveRemoteHandler = null;
         mAnimatingTransition = null;
-        mEnterTransitionMerged = false;
 
         mOnFinish.run();
         if (mFinishTransaction != null) {
@@ -382,17 +440,34 @@
                 || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
     }
 
+    /** Clean-up callbacks for transition. */
+    interface TransitionCallback {
+        /** Calls when the transition got consumed. */
+        default void onTransitionConsumed(boolean aborted) {}
+
+        /** Calls when the transition finished. */
+        default void onTransitionFinished(WindowContainerTransaction finishWct,
+                SurfaceControl.Transaction finishT) {}
+    }
+
+    /** Session for a transition and its clean-up callback. */
+    static class TransitSession {
+        final IBinder mTransition;
+        TransitionCallback mCallback;
+
+        TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+            mTransition = transition;
+            mCallback = callback != null ? callback : new TransitionCallback() {};
+        }
+    }
+
     /** Bundled information of dismiss transition. */
-    static class DismissTransition {
-        IBinder mTransition;
-
-        int mReason;
-
-        @SplitScreen.StageType
-        int mDismissTop;
+    static class DismissTransition extends TransitSession {
+        final int mReason;
+        final @SplitScreen.StageType int mDismissTop;
 
         DismissTransition(IBinder transition, int reason, int dismissTop) {
-            this.mTransition = transition;
+            super(transition, null /* callback */);
             this.mReason = reason;
             this.mDismissTop = dismissTop;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2229e26..3c7db33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,6 +24,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -214,6 +215,33 @@
                 }
             };
 
+    private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
+            new SplitScreenTransitions.TransitionCallback() {
+        @Override
+        public void onTransitionFinished(WindowContainerTransaction finishWct,
+                SurfaceControl.Transaction finishT) {
+            // Check if the recent transition is finished by returning to the current split, so we
+            // can restore the divider bar.
+            for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+                final WindowContainerTransaction.HierarchyOp op =
+                        finishWct.getHierarchyOps().get(i);
+                final IBinder container = op.getContainer();
+                if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+                        && (mMainStage.containsContainer(container)
+                        || mSideStage.containsContainer(container))) {
+                    setDividerVisibility(true, finishT);
+                    return;
+                }
+            }
+
+            // Dismiss the split screen if it's not returning to split.
+            prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+            setSplitsVisible(false);
+            setDividerVisibility(false, finishT);
+            logExit(EXIT_REASON_UNKNOWN);
+        }
+    };
+
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             ShellTaskOrganizer taskOrganizer, DisplayController displayController,
             DisplayImeController displayImeController,
@@ -337,15 +365,23 @@
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         targetStage.evictAllChildren(evictWct);
         targetStage.addTask(task, wct);
-        if (!evictWct.isEmpty()) {
-            wct.merge(evictWct, true /* transfer */);
-        }
 
         if (ENABLE_SHELL_TRANSITIONS) {
             prepareEnterSplitScreen(wct);
-            mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE,
-                    wct, null, this);
+            mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
+                    null, this, new SplitScreenTransitions.TransitionCallback() {
+                        @Override
+                        public void onTransitionFinished(WindowContainerTransaction finishWct,
+                                SurfaceControl.Transaction finishT) {
+                            if (!evictWct.isEmpty()) {
+                                finishWct.merge(evictWct, true);
+                            }
+                        }
+                    });
         } else {
+            if (!evictWct.isEmpty()) {
+                wct.merge(evictWct, true /* transfer */);
+            }
             mTaskOrganizer.applyTransaction(wct);
         }
         return true;
@@ -365,6 +401,39 @@
         return result;
     }
 
+    /** Launches an activity into split. */
+    void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+            @Nullable Bundle options) {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+        prepareEvictChildTasks(position, evictWct);
+
+        options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+        wct.sendPendingIntent(intent, fillInIntent, options);
+        prepareEnterSplitScreen(wct, null /* taskInfo */, position);
+
+        mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
+                new SplitScreenTransitions.TransitionCallback() {
+                    @Override
+                    public void onTransitionConsumed(boolean aborted) {
+                        // Switch the split position if launching as MULTIPLE_TASK failed.
+                        if (aborted
+                                && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+                            setSideStagePositionAnimated(
+                                    SplitLayout.reversePosition(mSideStagePosition));
+                        }
+                    }
+
+                    @Override
+                    public void onTransitionFinished(WindowContainerTransaction finishWct,
+                            SurfaceControl.Transaction finishT) {
+                        if (!evictWct.isEmpty()) {
+                            finishWct.merge(evictWct, true);
+                        }
+                    }
+                });
+    }
+
     /** Starts 2 tasks in one transition. */
     void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
             @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@@ -395,7 +464,7 @@
         wct.startTask(sideTaskId, sideOptions);
 
         mSplitTransitions.startEnterTransition(
-                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+                TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
     }
 
     /** Starts 2 tasks in one legacy transition. */
@@ -617,11 +686,13 @@
     }
 
     int getTaskId(@SplitPosition int splitPosition) {
-        if (mSideStagePosition == splitPosition) {
-            return mSideStage.getTopVisibleChildTaskId();
-        } else {
-            return mMainStage.getTopVisibleChildTaskId();
+        if (splitPosition == SPLIT_POSITION_UNDEFINED) {
+            return INVALID_TASK_ID;
         }
+
+        return mSideStagePosition == splitPosition
+                ? mSideStage.getTopVisibleChildTaskId()
+                : mMainStage.getTopVisibleChildTaskId();
     }
 
     void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
@@ -861,6 +932,7 @@
         mSplitLayout.init();
         setDividerVisibility(true, t);
         updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+        t.show(mRootTaskLeash);
         setSplitsVisible(true);
         mShouldUpdateRecents = true;
         updateRecentTasksSplitPair();
@@ -1211,7 +1283,7 @@
     private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
         final boolean hasChildren = stageListener.mHasChildren;
         final boolean isSideStage = stageListener == mSideStageListener;
-        if (!hasChildren && !mIsExiting) {
+        if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
             if (isSideStage && mMainStageListener.mVisible) {
                 // Exit to main stage if side stage no longer has children.
                 if (ENABLE_SHELL_TRANSITIONS) {
@@ -1231,7 +1303,7 @@
                             EXIT_REASON_APP_FINISHED);
                 }
             }
-        } else if (isSideStage && !mMainStage.isActive()) {
+        } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
             if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 mSideStage.removeAllTasks(wct, true);
@@ -1543,14 +1615,14 @@
                 } else if (activityType == ACTIVITY_TYPE_HOME
                         || activityType == ACTIVITY_TYPE_RECENTS) {
                     // Enter overview panel, so start recent transition.
-                    mSplitTransitions.setRecentTransition(transition,
-                            request.getRemoteTransition());
+                    mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
+                            mRecentTransitionCallback);
                 } else if (mSplitTransitions.mPendingRecent == null) {
                     // If split-task is not controlled by recents animation
                     // and occluded by the other fullscreen task, dismiss both.
                     prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
-                    mSplitTransitions.setDismissTransition(transition,
-                            STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
+                    mSplitTransitions.setDismissTransition(
+                            transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
                 }
             }
         } else {
@@ -1558,7 +1630,8 @@
                 // One task is appearing into split, prepare to enter split screen.
                 out = new WindowContainerTransaction();
                 prepareEnterSplitScreen(out);
-                mSplitTransitions.mPendingEnter = transition;
+                mSplitTransitions.setEnterTransition(
+                        transition, request.getRemoteTransition(), null /* callback */);
             }
         }
         return out;
@@ -1614,10 +1687,7 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (transition != mSplitTransitions.mPendingEnter
-                && transition != mSplitTransitions.mPendingRecent
-                && (mSplitTransitions.mPendingDismiss == null
-                        || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
+        if (!mSplitTransitions.isPendingTransition(transition)) {
             // Not entering or exiting, so just do some house-keeping and validation.
 
             // If we're not in split-mode, just abort so something else can handle it.
@@ -1664,12 +1734,11 @@
         }
 
         boolean shouldAnimate = true;
-        if (mSplitTransitions.mPendingEnter == transition) {
+        if (mSplitTransitions.isPendingEnter(transition)) {
             shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
-        } else if (mSplitTransitions.mPendingRecent == transition) {
+        } else if (mSplitTransitions.isPendingRecent(transition)) {
             shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
-        } else if (mSplitTransitions.mPendingDismiss != null
-                && mSplitTransitions.mPendingDismiss.mTransition == transition) {
+        } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
         }
@@ -1837,28 +1906,6 @@
         return true;
     }
 
-    void onRecentTransitionFinished(WindowContainerTransaction wct,
-            SurfaceControl.Transaction finishT) {
-        // Check if the recent transition is finished by returning to the current split so we can
-        // restore the divider bar.
-        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
-            final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
-            final IBinder container = op.getContainer();
-            if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
-                    && (mMainStage.containsContainer(container)
-                    || mSideStage.containsContainer(container))) {
-                setDividerVisibility(true, finishT);
-                return;
-            }
-        }
-
-        // Dismiss the split screen is it's not returning to split.
-        prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
-        setSplitsVisible(false);
-        setDividerVisibility(false, finishT);
-        logExit(EXIT_REASON_UNKNOWN);
-    }
-
     private void addDividerBarToTransition(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, boolean show) {
         final SurfaceControl leash = mSplitLayout.getDividerLeash();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dcd6277..0bec543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -483,11 +483,11 @@
                     postStartTransactionCallbacks.add(t ->
                             startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
                                     mTransactionPool, mMainExecutor, mAnimExecutor,
-                                    null /* position */, cornerRadius, clipRect));
+                                    change.getEndRelOffset(), cornerRadius, clipRect));
                 } else {
                     startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
-                            mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
-                            cornerRadius, clipRect);
+                            mTransactionPool, mMainExecutor, mAnimExecutor,
+                            change.getEndRelOffset(), cornerRadius, clipRect);
                 }
 
                 if (info.getAnimationOptions() != null) {
@@ -934,7 +934,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top),
+                mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
                 cornerRadius, change.getEndAbsBounds());
     }
 
@@ -959,7 +959,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, mAnimExecutor, null /* position */,
+                mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
                 cornerRadius, change.getEndAbsBounds());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index c19a33a..4855fbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -222,10 +222,10 @@
 
         float shadowRadius = outResult.mDensity * shadowRadiusDp;
         int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
-        mTmpColor[0] = Color.red(backgroundColorInt);
-        mTmpColor[1] = Color.green(backgroundColorInt);
-        mTmpColor[2] = Color.blue(backgroundColorInt);
-        t.setCrop(mTaskBackgroundSurface, taskBounds)
+        mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+        mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+        mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+        t.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
                 .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
                 .setColor(mTaskBackgroundSurface, mTmpColor);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
index 4922872..f8b3fb3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
@@ -34,13 +34,12 @@
      * given {@link SurfaceControl} when calling {@link SurfaceControl.Builder#build()}.
      *
      * @param mockSurfaceControl the first {@link SurfaceControl} to return
-     * @param mockSurfaceControls following {@link SurfaceControl} to return
      * @return the mock of {@link SurfaceControl.Builder}
      */
     public static SurfaceControl.Builder createMockSurfaceControlBuilder(
-            SurfaceControl mockSurfaceControl, SurfaceControl... mockSurfaceControls) {
+            SurfaceControl mockSurfaceControl) {
         final SurfaceControl.Builder mockBuilder = mock(SurfaceControl.Builder.class, RETURNS_SELF);
-        doReturn(mockSurfaceControl, (Object[]) mockSurfaceControls)
+        doReturn(mockSurfaceControl)
                 .when(mockBuilder)
                 .build();
         return mockBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 52d78ca..5880ffb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -37,6 +37,7 @@
 import android.view.SurfaceControl;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.wm.shell.MockSurfaceControlHelper;
 import com.android.wm.shell.ShellTestCase;
@@ -62,19 +63,18 @@
 
     @Mock
     private TaskInfo mTaskInfo;
-
     @Mock
     private PipAnimationController.PipAnimationCallback mPipAnimationCallback;
 
     @Before
     public void setUp() throws Exception {
-        mPipAnimationController = new PipAnimationController(
-                new PipSurfaceTransactionHelper());
+        MockitoAnnotations.initMocks(this);
+        mPipAnimationController = new PipAnimationController(new PipSurfaceTransactionHelper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
         mLeash = new SurfaceControl.Builder()
                 .setContainerLayer()
                 .setName("FakeLeash")
                 .build();
-        MockitoAnnotations.initMocks(this);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 304ca66..1d038f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -182,7 +182,7 @@
 
         IBinder transition = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
-                new RemoteTransition(testRemote), mStageCoordinator);
+                new RemoteTransition(testRemote), mStageCoordinator, null);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -422,7 +422,7 @@
         TransitionInfo enterInfo = createEnterPairInfo();
         IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
                 TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
-                new RemoteTransition(new TestRemoteTransition()), mStageCoordinator);
+                new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
         mMainStage.onTaskAppeared(mMainChild, createMockSurface());
         mSideStage.onTaskAppeared(mSideChild, createMockSurface());
         mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 680034bd..d1b837e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -21,23 +21,32 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
 import android.view.Display;
+import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager.LayoutParams;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
@@ -53,6 +62,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Supplier;
 
 /**
@@ -66,6 +77,8 @@
 public class WindowDecorationTests extends ShellTestCase {
     private static final int CAPTION_HEIGHT_DP = 32;
     private static final int SHADOW_RADIUS_DP = 5;
+    private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
+    private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
 
     private final Rect mOutsetsDp = new Rect();
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
@@ -84,12 +97,11 @@
     @Mock
     private WindowContainerTransaction mMockWindowContainerTransaction;
 
-    private SurfaceControl.Builder mMockSurfaceControlBuilder;
+    private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
     private SurfaceControl.Transaction mMockSurfaceControlTransaction;
 
     @Before
     public void setUp() {
-        mMockSurfaceControlBuilder = createMockSurfaceControlBuilder(mock(SurfaceControl.class));
         mMockSurfaceControlTransaction = createMockSurfaceControlTransaction();
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
@@ -97,6 +109,128 @@
     }
 
     @Test
+    public void testLayoutResultCalculation_invisibleTask() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+                createMockSurfaceControlBuilder(taskBackgroundSurface);
+        mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(false)
+                .build();
+        taskInfo.isFocused = false;
+        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+        // 64px.
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        mOutsetsDp.set(10, 20, 30, 40);
+
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        windowDecor.relayout(taskInfo);
+
+        verify(decorContainerSurfaceBuilder, never()).build();
+        verify(taskBackgroundSurfaceBuilder, never()).build();
+        verify(mMockSurfaceControlViewHostFactory, never())
+                .create(any(), any(), any(), anyBoolean());
+
+        verify(mMockSurfaceControlTransaction).hide(taskSurface);
+
+        assertNull(mRelayoutResult.mRootView);
+    }
+
+    @Test
+    public void testLayoutResultCalculation_visibleFocusedTask() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+                createMockSurfaceControlBuilder(taskBackgroundSurface);
+        mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(true)
+                .build();
+        taskInfo.isFocused = true;
+        // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+        // 64px.
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        mOutsetsDp.set(10, 20, 30, 40);
+
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+        windowDecor.relayout(taskInfo);
+
+        verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+        verify(decorContainerSurfaceBuilder).setContainerLayer();
+        verify(mMockSurfaceControlTransaction).setTrustedOverlay(decorContainerSurface, true);
+        verify(mMockSurfaceControlTransaction).setPosition(decorContainerSurface, -20, -40);
+        verify(mMockSurfaceControlTransaction).setWindowCrop(decorContainerSurface, 380, 220);
+
+        verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
+        verify(taskBackgroundSurfaceBuilder).setEffectLayer();
+        verify(mMockSurfaceControlTransaction).setWindowCrop(taskBackgroundSurface, 300, 100);
+        verify(mMockSurfaceControlTransaction)
+                .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
+        verify(mMockSurfaceControlTransaction).setShadowRadius(taskBackgroundSurface, 10);
+
+        verify(mMockSurfaceControlViewHostFactory)
+                .create(any(), eq(defaultDisplay), any(), anyBoolean());
+        verify(mMockSurfaceControlViewHost)
+                .setView(same(mMockView),
+                        argThat(lp -> lp.height == 64
+                                && lp.width == 300
+                                && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+        if (ViewRootImpl.CAPTION_ON_SHELL) {
+            verify(mMockView).setTaskFocusState(true);
+            verify(mMockWindowContainerTransaction)
+                    .addRectInsetsProvider(taskInfo.token,
+                            new Rect(100, 300, 400, 364),
+                            new int[] { InsetsState.ITYPE_CAPTION_BAR });
+        }
+
+        verify(mMockSurfaceControlTransaction)
+                .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+        verify(mMockSurfaceControlTransaction)
+                .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+        verify(mMockSurfaceControlTransaction)
+                .show(taskSurface);
+
+        assertEquals(380, mRelayoutResult.mWidth);
+        assertEquals(220, mRelayoutResult.mHeight);
+        assertEquals(2, mRelayoutResult.mDensity, 0.f);
+    }
+
+    @Test
     public void testNotCrashWhenDisplayAppearsAfterTask() {
         doReturn(mock(Display.class)).when(mMockDisplayController)
                 .getDisplay(Display.DEFAULT_DISPLAY);
@@ -145,10 +279,24 @@
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
         return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
-                taskInfo, testSurface, () -> mMockSurfaceControlBuilder,
+                taskInfo, testSurface, new MockSurfaceControlBuilderSupplier(),
                 mMockSurfaceControlViewHostFactory);
     }
 
+    private class MockSurfaceControlBuilderSupplier implements Supplier<SurfaceControl.Builder> {
+        private int mNumOfCalls = 0;
+
+        @Override
+        public SurfaceControl.Builder get() {
+            final SurfaceControl.Builder builder =
+                    mNumOfCalls < mMockSurfaceControlBuilders.size()
+                            ? mMockSurfaceControlBuilders.get(mNumOfCalls)
+                            : createMockSurfaceControlBuilder(mock(SurfaceControl.class));
+            ++mNumOfCalls;
+            return builder;
+        }
+    }
+
     private static class TestView extends View implements TaskFocusStateConsumer {
         private TestView(Context context) {
             super(context);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 30d6d00..19efc5f 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -21,6 +21,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/FontFileParser.h>
 #include <minikin/LocaleList.h>
+#include <minikin/MinikinFontFactory.h>
 #include <minikin/SystemFonts.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
@@ -206,9 +207,18 @@
     return entry;
 }
 
-static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader);
+class MinikinFontSkiaFactory : minikin::MinikinFontFactory {
+private:
+    MinikinFontSkiaFactory() : MinikinFontFactory() { MinikinFontFactory::setInstance(this); }
 
-static minikin::Font::TypefaceLoader* readMinikinFontSkia(minikin::BufferReader* reader) {
+public:
+    static void init() { static MinikinFontSkiaFactory factory; }
+    void skip(minikin::BufferReader* reader) const override;
+    std::shared_ptr<minikin::MinikinFont> create(minikin::BufferReader reader) const override;
+    void write(minikin::BufferWriter* writer, const minikin::MinikinFont* typeface) const override;
+};
+
+void MinikinFontSkiaFactory::skip(minikin::BufferReader* reader) const {
     // Advance reader's position.
     reader->skipString(); // fontPath
     reader->skip<int>(); // fontIndex
@@ -218,10 +228,10 @@
         reader->skip<uint32_t>(); // expectedFontRevision
         reader->skipString(); // expectedPostScriptName
     }
-    return &loadMinikinFontSkia;
 }
 
-static std::shared_ptr<minikin::MinikinFont> loadMinikinFontSkia(minikin::BufferReader reader) {
+std::shared_ptr<minikin::MinikinFont> MinikinFontSkiaFactory::create(
+        minikin::BufferReader reader) const {
     std::string_view fontPath = reader.readString();
     std::string path(fontPath.data(), fontPath.size());
     ATRACE_FORMAT("Loading font %s", path.c_str());
@@ -270,8 +280,8 @@
     return minikinFont;
 }
 
-static void writeMinikinFontSkia(minikin::BufferWriter* writer,
-        const minikin::MinikinFont* typeface) {
+void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer,
+                                   const minikin::MinikinFont* typeface) const {
     // When you change the format of font metadata, please update code to parse
     // typefaceMetadataReader() in
     // frameworks/base/libs/hwui/jni/fonts/Font.cpp too.
@@ -296,6 +306,7 @@
 }
 
 static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {
+    MinikinFontSkiaFactory::init();
     ScopedLongArrayRO faces(env, faceHandles);
     std::vector<Typeface*> typefaces;
     typefaces.reserve(faces.size());
@@ -312,7 +323,7 @@
             fontCollections.push_back(typeface->fFontCollection);
         }
     }
-    minikin::FontCollection::writeVector<writeMinikinFontSkia>(&writer, fontCollections);
+    minikin::FontCollection::writeVector(&writer, fontCollections);
     writer.write<uint32_t>(typefaces.size());
     for (Typeface* typeface : typefaces) {
       writer.write<uint32_t>(fcToIndex.find(typeface->fFontCollection)->second);
@@ -324,11 +335,12 @@
 }
 
 static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
+    MinikinFontSkiaFactory::init();
     void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
     if (addr == nullptr) return nullptr;
     minikin::BufferReader reader(addr);
     std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections =
-            minikin::FontCollection::readVector<readMinikinFontSkia>(&reader);
+            minikin::FontCollection::readVector(&reader);
     uint32_t typefaceCount = reader.read<uint32_t>();
     std::vector<jlong> faceHandles;
     faceHandles.reserve(typefaceCount);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 57f1056..a7a21e7 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -48,6 +48,7 @@
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
@@ -124,6 +125,7 @@
     private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
+    private final AtomicBoolean mIsScanning = new AtomicBoolean(/* initialValue= */ false);
 
     final Handler mHandler;
 
@@ -255,7 +257,9 @@
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void startScan() {
         if (isSystemRouter()) {
-            sManager.startScan();
+            if (!mIsScanning.getAndSet(true)) {
+                sManager.registerScanRequest();
+            }
         }
     }
 
@@ -281,7 +285,9 @@
     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void stopScan() {
         if (isSystemRouter()) {
-            sManager.stopScan();
+            if (mIsScanning.getAndSet(false)) {
+                sManager.unregisterScanRequest();
+            }
         }
     }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index c84f5b0..44c0b54 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -82,6 +82,7 @@
     @GuardedBy("sLock")
     private Client mClient;
     private final IMediaRouterService mMediaRouterService;
+    private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
     final Handler mHandler;
     final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
 
@@ -155,22 +156,18 @@
     }
 
     /**
-     * Starts scanning remote routes.
-     * <p>
-     * Route discovery can happen even when the {@link #startScan()} is not called.
-     * This is because the scanning could be started before by other apps.
-     * Therefore, calling this method after calling {@link #stopScan()} does not necessarily mean
-     * that the routes found before are removed and added again.
-     * <p>
-     * Use {@link Callback} to get the route related events.
-     * <p>
-     * @see #stopScan()
+     * Registers a request to scan for remote routes.
+     *
+     * <p>Increases the count of active scanning requests. When the count transitions from zero to
+     * one, sends a request to the system server to start scanning.
+     *
+     * <p>Clients must {@link #unregisterScanRequest() unregister their scan requests} when scanning
+     * is no longer needed, to avoid unnecessary resource usage.
      */
-    public void startScan() {
-        Client client = getOrCreateClient();
-        if (client != null) {
+    public void registerScanRequest() {
+        if (mScanRequestCount.getAndIncrement() == 0) {
             try {
-                mMediaRouterService.startScan(client);
+                mMediaRouterService.startScan(getOrCreateClient());
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -178,23 +175,26 @@
     }
 
     /**
-     * Stops scanning remote routes to reduce resource consumption.
-     * <p>
-     * Route discovery can be continued even after this method is called.
-     * This is because the scanning is only turned off when all the apps stop scanning.
-     * Therefore, calling this method does not necessarily mean the routes are removed.
-     * Also, for the same reason it does not mean that {@link Callback#onRoutesAdded(List)}
-     * is not called afterwards.
-     * <p>
-     * Use {@link Callback} to get the route related events.
+     * Unregisters a scan request made by {@link #registerScanRequest()}.
      *
-     * @see #startScan()
+     * <p>Decreases the count of active scanning requests. When the count transitions from one to
+     * zero, sends a request to the system server to stop scanning.
+     *
+     * @throws IllegalStateException If called while there are no active scan requests.
      */
-    public void stopScan() {
-        Client client = getOrCreateClient();
-        if (client != null) {
+    public void unregisterScanRequest() {
+        if (mScanRequestCount.updateAndGet(
+                count -> {
+                    if (count == 0) {
+                        throw new IllegalStateException(
+                                "No active scan requests to unregister.");
+                    } else {
+                        return --count;
+                    }
+                })
+                == 0) {
             try {
-                mMediaRouterService.stopScan(client);
+                mMediaRouterService.stopScan(getOrCreateClient());
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -423,15 +423,11 @@
      */
     @NonNull
     public List<RoutingSessionInfo> getRemoteSessions() {
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                return mMediaRouterService.getRemoteSessions(client);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            return mMediaRouterService.getRemoteSessions(getOrCreateClient());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
-        return Collections.emptyList();
     }
 
     /**
@@ -514,14 +510,12 @@
             return;
         }
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                int requestId = mNextRequestId.getAndIncrement();
-                mMediaRouterService.setRouteVolumeWithManager(client, requestId, route, volume);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            int requestId = mNextRequestId.getAndIncrement();
+            mMediaRouterService.setRouteVolumeWithManager(
+                    getOrCreateClient(), requestId, route, volume);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -543,15 +537,12 @@
             return;
         }
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                int requestId = mNextRequestId.getAndIncrement();
-                mMediaRouterService.setSessionVolumeWithManager(
-                        client, requestId, sessionInfo.getId(), volume);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            int requestId = mNextRequestId.getAndIncrement();
+            mMediaRouterService.setSessionVolumeWithManager(
+                    getOrCreateClient(), requestId, sessionInfo.getId(), volume);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -808,15 +799,12 @@
             return;
         }
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                int requestId = mNextRequestId.getAndIncrement();
-                mMediaRouterService.selectRouteWithManager(
-                        client, requestId, sessionInfo.getId(), route);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            int requestId = mNextRequestId.getAndIncrement();
+            mMediaRouterService.selectRouteWithManager(
+                    getOrCreateClient(), requestId, sessionInfo.getId(), route);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -850,15 +838,12 @@
             return;
         }
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                int requestId = mNextRequestId.getAndIncrement();
-                mMediaRouterService.deselectRouteWithManager(
-                        client, requestId, sessionInfo.getId(), route);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            int requestId = mNextRequestId.getAndIncrement();
+            mMediaRouterService.deselectRouteWithManager(
+                    getOrCreateClient(), requestId, sessionInfo.getId(), route);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -875,15 +860,12 @@
     public void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                int requestId = mNextRequestId.getAndIncrement();
-                mMediaRouterService.releaseSessionWithManager(
-                        client, requestId, sessionInfo.getId());
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            int requestId = mNextRequestId.getAndIncrement();
+            mMediaRouterService.releaseSessionWithManager(
+                    getOrCreateClient(), requestId, sessionInfo.getId());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -896,14 +878,11 @@
             @NonNull MediaRoute2Info route) {
         int requestId = createTransferRequest(session, route);
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                mMediaRouterService.transferToRouteWithManager(
-                        client, requestId, session.getId(), route);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            mMediaRouterService.transferToRouteWithManager(
+                    getOrCreateClient(), requestId, session.getId(), route);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
@@ -916,14 +895,11 @@
 
         int requestId = createTransferRequest(oldSession, route);
 
-        Client client = getOrCreateClient();
-        if (client != null) {
-            try {
-                mMediaRouterService.requestCreateSessionWithManager(
-                        client, requestId, oldSession, route);
-            } catch (RemoteException ex) {
-                throw ex.rethrowFromSystemServer();
-            }
+        try {
+            mMediaRouterService.requestCreateSessionWithManager(
+                    getOrCreateClient(), requestId, oldSession, route);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
         }
     }
 
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index b4aad9d..4086dec 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -39,6 +39,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import android.Manifest;
@@ -121,7 +122,7 @@
         MediaRouter2ManagerTestActivity.startActivity(mContext);
 
         mManager = MediaRouter2Manager.getInstance(mContext);
-        mManager.startScan();
+        mManager.registerScanRequest();
         mRouter2 = MediaRouter2.getInstance(mContext);
 
         // If we need to support thread pool executors, change this to thread pool executor.
@@ -152,7 +153,7 @@
 
     @After
     public void tearDown() {
-        mManager.stopScan();
+        mManager.unregisterScanRequest();
 
         // order matters (callbacks should be cleared at the last)
         releaseAllSessions();
@@ -818,6 +819,13 @@
         assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
     }
 
+    @Test
+    public void unregisterScanRequest_enforcesANonNegativeCount() {
+        mManager.unregisterScanRequest(); // One request was made in the test setup.
+        assertThrows(IllegalStateException.class, () -> mManager.unregisterScanRequest());
+        mManager.registerScanRequest(); // So that the cleanup doesn't fail.
+    }
+
     /**
      * Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on
      * selected routes
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 58944f6..4714ff9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -576,6 +576,15 @@
             return;
         }
 
+        // The profiles list's sequence will affect the bluetooth icon at
+        // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice).
+
+        // Moving the LE audio profile to be the first priority if the device supports LE audio.
+        if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
+            profiles.add(mLeAudioProfile);
+            removedProfiles.remove(mLeAudioProfile);
+        }
+
         if (mHeadsetProfile != null) {
             if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG)
                     && ArrayUtils.contains(uuids, BluetoothUuid.HSP))
@@ -660,11 +669,6 @@
             removedProfiles.remove(mHearingAidProfile);
         }
 
-        if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
-            profiles.add(mLeAudioProfile);
-            removedProfiles.remove(mLeAudioProfile);
-        }
-
         if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
             profiles.add(mSapProfile);
             removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 192872f..d9262cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -92,14 +92,14 @@
     public void startScan() {
         mMediaDevices.clear();
         mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
-        mRouterManager.startScan();
+        mRouterManager.registerScanRequest();
         refreshDevices();
     }
 
     @Override
     public void stopScan() {
         mRouterManager.unregisterCallback(mMediaRouterCallback);
-        mRouterManager.stopScan();
+        mRouterManager.unregisterScanRequest();
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 4332bd2..45c0d78 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -57,6 +57,7 @@
     private final Handler mHandler;
     private final Handler mMainThreadHandler;
     private final Set<Integer> mNetworks = new HashSet<>();
+    private int mPrimaryNetworkId;
     // Save the previous HISTORY_SIZE states for logging.
     private final String[] mHistory = new String[HISTORY_SIZE];
     // Where to copy the next state into.
@@ -106,6 +107,7 @@
             if (!mNetworks.contains(network.getNetId())) {
                 mNetworks.add(network.getNetId());
             }
+            mPrimaryNetworkId = network.getNetId();
             updateWifiInfo(wifiInfo);
             updateStatusLabel();
             mMainThreadHandler.post(() -> postResults());
@@ -121,10 +123,13 @@
             recordLastWifiNetwork(log);
             if (mNetworks.contains(network.getNetId())) {
                 mNetworks.remove(network.getNetId());
-                updateWifiInfo(null);
-                updateStatusLabel();
-                mMainThreadHandler.post(() -> postResults());
             }
+            if (network.getNetId() != mPrimaryNetworkId) {
+                return;
+            }
+            updateWifiInfo(null);
+            updateStatusLabel();
+            mMainThreadHandler.post(() -> postResults());
         }
     };
     private final NetworkCallback mDefaultNetworkCallback =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
new file mode 100644
index 0000000..dc7e313d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiStatusTrackerTest {
+    @Mock Context mContext;
+    @Mock WifiManager mWifiManager;
+    @Mock NetworkScoreManager mNetworkScoreManager;
+    @Mock ConnectivityManager mConnectivityManager;
+    @Mock Runnable mCallback;
+
+    private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
+            mNetworkCallbackCaptor =
+            ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Verify that we only clear the WifiInfo if the primary network was lost.
+     */
+    @Test
+    public void testWifiInfoClearedOnPrimaryNetworkLost() {
+        WifiStatusTracker wifiStatusTracker = new WifiStatusTracker(mContext, mWifiManager,
+                mNetworkScoreManager, mConnectivityManager, mCallback);
+        wifiStatusTracker.setListening(true);
+
+        verify(mConnectivityManager)
+                .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
+
+        // Trigger a validation callback for the primary Wifi network.
+        WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(primaryWifiInfo.makeCopy(anyLong())).thenReturn(primaryWifiInfo);
+        when(primaryWifiInfo.isPrimary()).thenReturn(true);
+        int primaryRssi = -55;
+        when(primaryWifiInfo.getRssi()).thenReturn(primaryRssi);
+        NetworkCapabilities primaryCap = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .setTransportInfo(primaryWifiInfo)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+                .build();
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        // Verify primary wifi info is the one being used.
+        assertThat(wifiStatusTracker.connected).isTrue();
+        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+        // Trigger a validation callback for the non-primary Wifi network.
+        WifiInfo nonPrimaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(nonPrimaryWifiInfo.makeCopy(anyLong())).thenReturn(nonPrimaryWifiInfo);
+        when(nonPrimaryWifiInfo.isPrimary()).thenReturn(false);
+        int nonPrimaryRssi = -75;
+        when(primaryWifiInfo.getRssi()).thenReturn(nonPrimaryRssi);
+        NetworkCapabilities nonPrimaryCap = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .setTransportInfo(nonPrimaryWifiInfo)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+                .build();
+        Network nonPrimaryNetwork = Mockito.mock(Network.class);
+        int nonPrimaryNetworkId = 2;
+        when(nonPrimaryNetwork.getNetId()).thenReturn(nonPrimaryNetworkId);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(nonPrimaryNetwork, nonPrimaryCap);
+
+        // Verify primary wifi info is still the one being used.
+        assertThat(wifiStatusTracker.connected).isTrue();
+        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+        // Lose the non-primary network.
+        mNetworkCallbackCaptor.getValue().onLost(nonPrimaryNetwork);
+
+        // Verify primary wifi info is still the one being used.
+        assertThat(wifiStatusTracker.connected).isTrue();
+        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+        // Lose the primary network.
+        mNetworkCallbackCaptor.getValue().onLost(primaryNetwork);
+
+        // Verify we aren't connected anymore.
+        assertThat(wifiStatusTracker.connected).isFalse();
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
index 4d5014c..b8639e6 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
@@ -37,7 +37,7 @@
  * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
  * most of the colors in this class will be removed in favor of their M3 counterpart.
  */
-class AndroidColorScheme internal constructor(private val context: Context) {
+class AndroidColorScheme internal constructor(context: Context) {
     val colorPrimary = getColor(context, R.attr.colorPrimary)
     val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
     val colorAccent = getColor(context, R.attr.colorAccent)
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index bddc1e6..40504dc 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -34,6 +34,7 @@
     ],
 
     static_libs: [
+        "SystemUI-core",
         "SystemUIComposeCore",
         "SystemUIComposeFeatures",
 
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
index 4fcce0b..2f30651 100644
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -16,7 +16,40 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.android.systemui.compose.gallery">
     <!-- To emulate a display size and density. -->
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <application
+        android:name="android.app.Application"
+        android:appComponentFactory="androidx.core.app.AppComponentFactory"
+        tools:replace="android:name,android:appComponentFactory">
+        <!-- Disable providers from SystemUI -->
+        <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+            android:authorities="com.android.systemui.test.keyguard.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+            android:authorities="com.android.systemui.test.keyguard.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+            android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="com.android.systemui.people.PeopleProvider"
+            android:authorities="com.android.systemui.test.people.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove" />
+        <provider android:name="androidx.core.content.FileProvider"
+            android:authorities="com.android.systemui.test.fileprovider.disabled"
+            android:enabled="false"
+            tools:replace="android:authorities"
+            tools:node="remove"/>
+    </application>
 </manifest>
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
index e7d496c..1f3fd8c 100644
--- a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -16,6 +16,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.android.systemui.compose.gallery.app">
     <application
         android:allowBackup="true"
@@ -23,7 +24,8 @@
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:theme="@style/Theme.SystemUIGallery">
+        android:theme="@style/Theme.SystemUI.Gallery"
+        tools:replace="android:icon,android:theme,android:label">
         <activity
             android:name="com.android.systemui.compose.gallery.GalleryActivity"
             android:exported="true"
diff --git a/packages/SystemUI/compose/gallery/res/values-night/themes.xml b/packages/SystemUI/compose/gallery/res/values-night/themes.xml
deleted file mode 100644
index 58d20b8..0000000
--- a/packages/SystemUI/compose/gallery/res/values-night/themes.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
--->
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="Theme.SystemUIGallery" parent="Theme.AppCompat.DayNight.NoActionBar">
-        <item name="android:statusBarColor" tools:targetApi="l">
-            @android:color/transparent
-        </item>
-        <item name="android:navigationBarColor" tools:targetApi="l">
-            @android:color/transparent
-        </item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml
index 6e5e9983..45fa1f5d 100644
--- a/packages/SystemUI/compose/gallery/res/values/themes.xml
+++ b/packages/SystemUI/compose/gallery/res/values/themes.xml
@@ -15,7 +15,10 @@
      limitations under the License.
 -->
 <resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="Theme.SystemUIGallery" parent="Theme.AppCompat.DayNight.NoActionBar">
+    <style name="Theme.SystemUI.Gallery">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+
         <item name="android:statusBarColor" tools:targetApi="l">
             @android:color/transparent
         </item>
@@ -24,4 +27,4 @@
         </item>
         <item name="android:windowLightStatusBar">true</item>
     </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
index 06bdad8..990d060 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt
@@ -9,11 +9,12 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.BrightnessHigh
-import androidx.compose.material.icons.filled.BrightnessLow
+import androidx.compose.material.icons.filled.DarkMode
 import androidx.compose.material.icons.filled.FormatSize
 import androidx.compose.material.icons.filled.FormatTextdirectionLToR
 import androidx.compose.material.icons.filled.FormatTextdirectionRToL
+import androidx.compose.material.icons.filled.InvertColors
+import androidx.compose.material.icons.filled.LightMode
 import androidx.compose.material.icons.filled.Smartphone
 import androidx.compose.material.icons.filled.Tablet
 import androidx.compose.material3.Button
@@ -44,12 +45,13 @@
 /** A configuration panel that allows to toggle the theme, font scale and layout direction. */
 @Composable
 fun ConfigurationControls(
-    isDarkTheme: Boolean,
+    theme: Theme,
     fontScale: FontScale,
     layoutDirection: LayoutDirection,
     onChangeTheme: () -> Unit,
     onChangeLayoutDirection: () -> Unit,
     onChangeFontScale: () -> Unit,
+    modifier: Modifier = Modifier,
 ) {
     // The display we are emulating, if any.
     var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) }
@@ -84,18 +86,26 @@
 
     // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users
     // don't miss any available configuration.
-    LazyRow {
+    LazyRow(modifier) {
         // Dark/light theme.
         item {
             TextButton(onChangeTheme) {
                 val text: String
                 val icon: ImageVector
-                if (isDarkTheme) {
-                    icon = Icons.Default.BrightnessHigh
-                    text = "Dark"
-                } else {
-                    icon = Icons.Default.BrightnessLow
-                    text = "Light"
+
+                when (theme) {
+                    Theme.System -> {
+                        icon = Icons.Default.InvertColors
+                        text = "System"
+                    }
+                    Theme.Dark -> {
+                        icon = Icons.Default.DarkMode
+                        text = "Dark"
+                    }
+                    Theme.Light -> {
+                        icon = Icons.Default.LightMode
+                        text = "Light"
+                    }
                 }
 
                 Icon(icon, null)
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
index d18b454..bb2d2fe 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.compose.gallery
 
+import android.app.UiModeManager
+import android.content.Context
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
@@ -23,7 +25,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.graphics.Color
 import androidx.core.view.WindowCompat
@@ -33,22 +35,46 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.setDecorFitsSystemWindows(window, false)
+        val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
 
         setContent {
-            val isSystemInDarkTheme = isSystemInDarkTheme()
-            var isDarkTheme by remember { mutableStateOf(isSystemInDarkTheme) }
-            val onChangeTheme = { isDarkTheme = !isDarkTheme }
+            var theme by rememberSaveable { mutableStateOf(Theme.System) }
+            val onChangeTheme = {
+                // Change to the next theme for a toggle behavior.
+                theme =
+                    when (theme) {
+                        Theme.System -> Theme.Dark
+                        Theme.Dark -> Theme.Light
+                        Theme.Light -> Theme.System
+                    }
+            }
 
+            val isSystemInDarkTheme = isSystemInDarkTheme()
+            val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme)
+            val useDarkIcons = !isDark
             val systemUiController = rememberSystemUiController()
-            val useDarkIcons = !isDarkTheme
             SideEffect {
                 systemUiController.setSystemBarsColor(
                     color = Color.Transparent,
                     darkIcons = useDarkIcons,
                 )
+
+                uiModeManager.setApplicationNightMode(
+                    when (theme) {
+                        Theme.System -> UiModeManager.MODE_NIGHT_AUTO
+                        Theme.Dark -> UiModeManager.MODE_NIGHT_YES
+                        Theme.Light -> UiModeManager.MODE_NIGHT_NO
+                    }
+                )
             }
 
-            GalleryApp(isDarkTheme, onChangeTheme)
+            GalleryApp(theme, onChangeTheme)
         }
     }
 }
+
+enum class Theme {
+    System,
+    Dark,
+    Light,
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
index 0ac3a63..c341867 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -23,36 +23,38 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import com.android.systemui.compose.theme.SystemUITheme
 
-enum class Screen {
-    Home,
-    Typography,
-    MaterialColors,
-    AndroidColors,
-    ExampleFeature,
+/** The gallery app screens. */
+object GalleryAppScreens {
+    val Typography = ChildScreen("typography") { TypographyScreen() }
+    val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() }
+    val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
+    val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
+
+    val Home =
+        ParentScreen(
+            "home",
+            mapOf(
+                "Typography" to Typography,
+                "Material colors" to MaterialColors,
+                "Android colors" to AndroidColors,
+                "Example feature" to ExampleFeature,
+            )
+        )
 }
 
-/** The main content of the app, that shows the [HomeScreen] by default. */
+/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
 @Composable
 private fun MainContent() {
     Box(Modifier.fillMaxSize()) {
         val navController = rememberNavController()
         NavHost(
             navController = navController,
-            startDestination = Screen.Home.name,
+            startDestination = GalleryAppScreens.Home.identifier,
         ) {
-            composable(Screen.Home.name) {
-                HomeScreen(
-                    onScreenSelected = { navController.navigate(it.name) },
-                )
-            }
-            composable(Screen.Typography.name) { TypographyScreen() }
-            composable(Screen.MaterialColors.name) { MaterialColorsScreen() }
-            composable(Screen.AndroidColors.name) { AndroidColorsScreen() }
-            composable(Screen.ExampleFeature.name) { ExampleFeatureScreen() }
+            screen(GalleryAppScreens.Home, navController)
         }
     }
 }
@@ -63,7 +65,7 @@
  */
 @Composable
 fun GalleryApp(
-    isDarkTheme: Boolean,
+    theme: Theme,
     onChangeTheme: () -> Unit,
 ) {
     val systemFontScale = LocalDensity.current.fontScale
@@ -98,14 +100,14 @@
         LocalDensity provides density,
         LocalLayoutDirection provides layoutDirection,
     ) {
-        SystemUITheme(isDarkTheme) {
+        SystemUITheme {
             Surface(
                 Modifier.fillMaxSize(),
                 color = MaterialTheme.colorScheme.background,
             ) {
                 Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
                     ConfigurationControls(
-                        isDarkTheme,
+                        theme,
                         fontScale,
                         layoutDirection,
                         onChangeTheme,
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/HomeScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/HomeScreen.kt
deleted file mode 100644
index b1869da..0000000
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/HomeScreen.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.compose.gallery
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-/** The home screen shown when starting the app. */
-@Composable
-fun HomeScreen(onScreenSelected: (Screen) -> Unit) {
-    LazyColumn(
-        verticalArrangement = Arrangement.spacedBy(8.dp),
-    ) {
-        Screen.values()
-            .filter { it != Screen.Home }
-            .forEach { screen ->
-                item {
-                    Surface(
-                        Modifier.fillMaxWidth(),
-                        color = MaterialTheme.colorScheme.secondaryContainer,
-                        shape = CircleShape,
-                    ) {
-                        Column(
-                            Modifier.clickable { onScreenSelected(screen) }.padding(16.dp),
-                            horizontalAlignment = Alignment.CenterHorizontally,
-                        ) {
-                            Text(screen.name)
-                        }
-                    }
-                }
-            }
-    }
-}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
new file mode 100644
index 0000000..467dac04
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.compose.gallery
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.navigation
+
+/**
+ * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to
+ * them or a [ChildScreen] which shows some content.
+ */
+sealed class Screen(val identifier: String)
+
+class ParentScreen(
+    identifier: String,
+    val children: Map<String, Screen>,
+) : Screen(identifier)
+
+class ChildScreen(
+    identifier: String,
+    val content: @Composable (NavController) -> Unit,
+) : Screen(identifier)
+
+/** Create the navigation graph for [screen]. */
+fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
+    when (screen) {
+        is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
+        is ParentScreen -> {
+            val menuRoute = "${screen.identifier}_menu"
+            navigation(startDestination = menuRoute, route = screen.identifier) {
+                // The menu to navigate to one of the children screens.
+                composable(menuRoute) { ScreenMenu(screen, navController) }
+
+                // The content of the child screens.
+                screen.children.forEach { (_, child) -> screen(child, navController) }
+            }
+        }
+    }
+}
+
+@Composable
+private fun ScreenMenu(
+    screen: ParentScreen,
+    navController: NavController,
+) {
+    LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+        screen.children.forEach { (name, child) ->
+            item {
+                Surface(
+                    Modifier.fillMaxWidth(),
+                    color = MaterialTheme.colorScheme.secondaryContainer,
+                    shape = CircleShape,
+                ) {
+                    Column(
+                        Modifier.clickable { navController.navigate(child.identifier) }
+                            .padding(16.dp),
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                    ) {
+                        Text(name)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index e3f5687..4222744 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.recents.model;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
@@ -245,12 +246,16 @@
      */
     public static Task from(TaskKey taskKey, TaskInfo taskInfo, boolean isLocked) {
         ActivityManager.TaskDescription td = taskInfo.taskDescription;
+        // Also consider undefined activity type to include tasks in overview right after rebooting
+        // the device.
+        final boolean isDockable = taskInfo.supportsMultiWindow
+                && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode())
+                && (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
+                || ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
         return new Task(taskKey,
                 td != null ? td.getPrimaryColor() : 0,
-                td != null ? td.getBackgroundColor() : 0, taskInfo.supportsMultiWindow
-                && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
-                && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()),
-                isLocked, td, taskInfo.topActivity);
+                td != null ? td.getBackgroundColor() : 0, isDockable , isLocked, td,
+                taskInfo.topActivity);
     }
 
     public Task(TaskKey key) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index 8c8f54f..ad073c0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -49,8 +49,8 @@
 ) : UserFileManager, CoreStartable(context) {
     companion object {
         private const val FILES = "files"
-        private const val SHARED_PREFS = "shared_prefs"
-        internal const val ID = "UserFileManager"
+        @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+        @VisibleForTesting internal const val ID = "UserFileManager"
     }
 
    private val broadcastReceiver = object : BroadcastReceiver() {
@@ -85,13 +85,15 @@
                 fileName
             )
         } else {
-            Environment.buildPath(
+            val secondaryFile = Environment.buildPath(
                 context.filesDir,
                 ID,
                 userId.toString(),
                 FILES,
                 fileName
             )
+            ensureParentDirExists(secondaryFile)
+            secondaryFile
         }
     }
 
@@ -114,6 +116,7 @@
             fileName
         )
 
+        ensureParentDirExists(secondaryUserDir)
         return context.getSharedPreferences(secondaryUserDir, mode)
     }
 
@@ -141,4 +144,18 @@
             }
         }
     }
+
+    /**
+     * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+     * recursively.
+     */
+    @VisibleForTesting
+    internal fun ensureParentDirExists(file: File) {
+        val parent = file.parentFile
+        if (!parent.exists()) {
+            if (!parent.mkdirs()) {
+                Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 2e66648..dfcdaef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,7 +62,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.GuestResetOrExitSessionReceiver;
 import com.android.systemui.GuestResumeSessionReceiver;
@@ -167,6 +166,7 @@
     private final AtomicBoolean mGuestIsResetting;
     private final AtomicBoolean mGuestCreationScheduled;
     private FalsingManager mFalsingManager;
+    @Nullable
     private View mView;
     private String mCreateSupervisedUserPackage;
     private GlobalSettings mGlobalSettings;
@@ -572,9 +572,11 @@
 
     protected void switchToUserId(int id) {
         try {
-            mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
-                    .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
-                    .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+            if (mView != null) {
+                mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
+                        .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
+                        .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+            }
             mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
             pauseRefreshUsers();
             mActivityManager.switchUser(id);
@@ -936,15 +938,17 @@
         guestCreationProgressDialog.show();
 
         // userManager.createGuest will block the thread so post is needed for the dialog to show
-        ThreadUtils.postOnMainThread(() -> {
+        mBgExecutor.execute(() -> {
             final int guestId = createGuest();
-            guestCreationProgressDialog.dismiss();
-            if (guestId == UserHandle.USER_NULL) {
-                Toast.makeText(mContext,
-                        com.android.settingslib.R.string.add_guest_failed,
-                        Toast.LENGTH_SHORT).show();
-            }
-            callback.accept(guestId);
+            mUiExecutor.execute(() -> {
+                guestCreationProgressDialog.dismiss();
+                if (guestId == UserHandle.USER_NULL) {
+                    Toast.makeText(mContext,
+                            com.android.settingslib.R.string.add_guest_failed,
+                            Toast.LENGTH_SHORT).show();
+                }
+                callback.accept(guestId);
+            });
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
deleted file mode 100644
index 0686071c..0000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * A class to do the user creation process. It shows a progress dialog, and manages the user
- * creation
- */
-public class UserCreator {
-
-    private final Context mContext;
-    private final UserManager mUserManager;
-
-    @Inject
-    public UserCreator(Context context, UserManager userManager) {
-        mContext = context;
-        mUserManager = userManager;
-    }
-
-    /**
-     * Shows a progress dialog then starts the user creation process on the main thread.
-     *
-     * @param successCallback is called when the user creation is successful.
-     * @param errorCallback   is called when userManager.createUser returns null.
-     *                        (Exceptions are not handled by this class)
-     */
-    public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
-            Runnable errorCallback) {
-
-        Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
-        userCreationProgressDialog.show();
-
-        // userManager.createUser will block the thread so post is needed for the dialog to show
-        ThreadUtils.postOnMainThread(() -> {
-            UserInfo user =
-                    mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
-            if (user == null) {
-                // Couldn't create user for some reason
-                userCreationProgressDialog.dismiss();
-                errorCallback.run();
-                return;
-            }
-
-            Drawable newUserIcon = userIcon;
-            Resources res = mContext.getResources();
-            if (newUserIcon == null) {
-                newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
-            }
-            mUserManager.setUserIcon(
-                    user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
-
-            userCreationProgressDialog.dismiss();
-            successCallback.accept(user);
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
new file mode 100644
index 0000000..dcbbe74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.drawable.Drawable
+import android.os.UserManager
+import com.android.internal.util.UserIcons
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+class UserCreator @Inject constructor(
+    private val context: Context,
+    private val userManager: UserManager,
+    @Main private val mainExecutor: Executor,
+    @Background private val bgExecutor: Executor
+) {
+    /**
+     * Shows a progress dialog then starts the user creation process on the main thread.
+     *
+     * @param successCallback is called when the user creation is successful.
+     * @param errorCallback is called when userManager.createUser returns null.
+     * (Exceptions are not handled by this class)
+     */
+    fun createUser(
+        userName: String?,
+        userIcon: Drawable?,
+        successCallback: Consumer<UserInfo?>,
+       errorCallback: Runnable
+    ) {
+        val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
+        userCreationProgressDialog.show()
+
+        // userManager.createUser will block the thread so post is needed for the dialog to show
+        bgExecutor.execute {
+            val user = userManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+            mainExecutor.execute main@{
+                if (user == null) {
+                    // Couldn't create user for some reason
+                    userCreationProgressDialog.dismiss()
+                    errorCallback.run()
+                    return@main
+                }
+                bgExecutor.execute {
+                    var newUserIcon = userIcon
+                    val res = context.resources
+                    if (newUserIcon == null) {
+                        newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
+                    }
+                    userManager.setUserIcon(
+                        user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
+                }
+                userCreationProgressDialog.dismiss()
+                successCallback.accept(user)
+            }
+        }
+    }
+}
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 d414660..533c231 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
@@ -233,46 +233,29 @@
     }
 
     @Test
-    public void onStart_isBroadcasting_verifyRegisterLeBroadcastServiceCallBack() {
+    public void whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         mIsBroadcasting = true;
 
         mMediaOutputBaseDialogImpl.onStart();
-
         verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
-    }
-
-    @Test
-    public void onStart_notBroadcasting_noRegisterLeBroadcastServiceCallBack() {
-        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
-                mLocalBluetoothLeBroadcast);
-        mIsBroadcasting = false;
-
-        mMediaOutputBaseDialogImpl.onStart();
-
-        verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
-    }
-
-    @Test
-    public void onStart_isBroadcasting_verifyUnregisterLeBroadcastServiceCallBack() {
-        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
-                mLocalBluetoothLeBroadcast);
-        mIsBroadcasting = true;
 
         mMediaOutputBaseDialogImpl.onStop();
-
         verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
     }
 
     @Test
-    public void onStop_notBroadcasting_noUnregisterLeBroadcastServiceCallBack() {
+    public void
+            whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsNotRegisteredOrUnregistered() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         mIsBroadcasting = false;
 
+        mMediaOutputBaseDialogImpl.onStart();
         mMediaOutputBaseDialogImpl.onStop();
 
+        verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any());
         verify(mLocalBluetoothLeBroadcast, never()).unregisterServiceCallBack(any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 73226fa..6d9b01e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,6 +63,14 @@
             broadcastDispatcher, backgroundExecutor)
     }
 
+    @After
+    fun end() {
+        val dir = Environment.buildPath(
+            context.filesDir,
+            UserFileManagerImpl.ID)
+        dir.deleteRecursively()
+    }
+
     @Test
     fun testGetFile() {
         assertThat(userFileManager.getFile(TEST_FILE_NAME, 0).path)
@@ -72,8 +81,19 @@
 
     @Test
     fun testGetSharedPreferences() {
+        val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
+        val secondaryUserDir = Environment.buildPath(
+            context.filesDir,
+            UserFileManagerImpl.ID,
+            "11",
+            UserFileManagerImpl.SHARED_PREFS,
+            TEST_FILE_NAME
+        )
+
+        assertThat(secondarySharedPref).isNotNull()
+        assertThat(secondaryUserDir.exists())
         assertThat(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 0))
-            .isNotEqualTo(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11))
+            .isNotEqualTo(secondarySharedPref)
     }
 
     @Test
@@ -115,6 +135,19 @@
         verify(userManager).aliveUsers
         assertThat(secondaryUserDir.exists()).isFalse()
         assertThat(file.exists()).isFalse()
-        dir.deleteRecursively()
+    }
+
+    @Test
+    fun testEnsureParentDirExists() {
+        val file = Environment.buildPath(
+            context.filesDir,
+            UserFileManagerImpl.ID,
+            "11",
+            "files",
+            TEST_FILE_NAME
+        )
+        assertThat(file.parentFile.exists()).isFalse()
+        userFileManager.ensureParentDirExists(file)
+        assertThat(file.parentFile.exists()).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 09d7c03..359a780 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -77,6 +77,7 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -269,6 +270,8 @@
         `when`(userManager.createGuest(any())).thenReturn(guestInfo)
 
         userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
+        bgExecutor.runAllReady()
+        uiExecutor.runAllReady()
         testableLooper.processAllMessages()
         verify(interactionJankMonitor).begin(any())
         verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -294,6 +297,8 @@
         `when`(userManager.createGuest(any())).thenReturn(guestInfo)
 
         userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+        bgExecutor.runAllReady()
+        uiExecutor.runAllReady()
         testableLooper.processAllMessages()
         verify(dialogShower).dismiss()
     }
@@ -584,4 +589,24 @@
         broadcastReceiverCaptor.value.onReceive(context, intent)
         verify(cb).onUserSwitched()
     }
+
+    @Test
+    fun onUserItemClicked_guest_runsOnBgThread() {
+        val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
+        val guestUserRecord = UserSwitcherController.UserRecord(
+            null,
+            picture,
+            true /* guest */,
+            false /* current */,
+            false /* isAddUser */,
+            false /* isRestricted */,
+            true /* isSwitchToEnabled */,
+            false /* isAddSupervisedUser */)
+
+        userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
+        assertTrue(bgExecutor.numPending() > 0)
+        verify(userManager, never()).createGuest(context)
+        bgExecutor.runAllReady()
+        verify(userManager).createGuest(context)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
new file mode 100644
index 0000000..a85ae7df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.user
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserCreatorTest : SysuiTestCase() {
+    companion object {
+        const val USER_NAME = "abc"
+    }
+
+    @Mock
+    private lateinit var userCreator: UserCreator
+    @Mock
+    private lateinit var userManager: UserManager
+    private lateinit var mainExecutor: FakeExecutor
+    private lateinit var bgExecutor: FakeExecutor
+    private lateinit var user: UserInfo
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mainExecutor = FakeExecutor(FakeSystemClock())
+        bgExecutor = FakeExecutor(FakeSystemClock())
+        userCreator = UserCreator(context, userManager, mainExecutor, bgExecutor)
+        user = Mockito.mock(UserInfo::class.java)
+        Mockito.`when`(userManager.createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0))
+            .thenReturn(user)
+    }
+
+    @Test
+    fun testCreateUser_threadingOrder() {
+        val successCallback = Mockito.mock(Consumer::class.java)
+        val errorCallback = Mockito.mock(Runnable::class.java)
+
+        userCreator.createUser(
+            USER_NAME,
+            null,
+            successCallback as Consumer<UserInfo?>,
+            errorCallback)
+
+        verify(userManager, never()).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+        bgExecutor.runAllReady()
+        verify(successCallback, never()).accept(user)
+        mainExecutor.runAllReady()
+        verify(userManager, never()).setUserIcon(anyInt(), any(Bitmap::class.java))
+        bgExecutor.runAllReady()
+
+        verify(userManager).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+        verify(userManager).setUserIcon(anyInt(), any(Bitmap::class.java))
+        verify(successCallback).accept(user)
+    }
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 73d9cc7..d29e25c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -862,10 +862,10 @@
         return list;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.SHUTDOWN)
     @Override
     public void shutdown() {
         // TODO: remove from aidl if nobody calls externally
-        mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG);
 
         Slog.i(TAG, "Shutting down");
     }
@@ -1203,9 +1203,9 @@
         setUidOnMeteredNetworkList(uid, true, enable);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.NETWORK_SETTINGS)
     @Override
     public boolean setDataSaverModeEnabled(boolean enable) {
-        mContext.enforceCallingOrSelfPermission(NETWORK_SETTINGS, TAG);
 
         if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
         synchronized (mQuotaLock) {
@@ -1741,9 +1741,9 @@
         return NetdUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
     @Override
     public boolean isNetworkRestricted(int uid) {
-        mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
         return isNetworkRestrictedInternal(uid);
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 7371d07..33e4070 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -564,10 +564,9 @@
         return res;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
-        mAm.mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
         Parcel current = Parcel.obtain();
         synchronized (mLock) {
             long now = SystemClock.uptimeMillis();
@@ -619,11 +618,10 @@
      * @return List of proto binary of individual commit files or one that is merged from them;
      *         the merged, final ProcessStats object.
      */
+    @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public long getCommittedStatsMerged(long highWaterMarkMs, int section, boolean doAggregate,
             List<ParcelFileDescriptor> committedStats, ProcessStats mergedStats) {
-        mAm.mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
 
         long newHighWaterMark = highWaterMarkMs;
         mFileLock.lock();
@@ -708,10 +706,9 @@
         return fds[0];
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public ParcelFileDescriptor getStatsOverTime(long minTime) {
-        mAm.mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
         Parcel current = Parcel.obtain();
         long curTime;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 44aefcc..fe9e68d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -897,17 +897,6 @@
                     throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
                             + " when it is not targeted into to a specific uid.");
                 }
-                // TODO(b/228161340): Remove the fallback of targeting injection into all windows
-                //  when the caller has the injection permission.
-                // Explicitly maintain the same behavior as previous versions of Android, where
-                // injection is allowed into all windows if the caller has the INJECT_EVENTS
-                // permission, even if it is targeting a certain uid.
-                if (checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
-                        "injectInputEvent-target-mismatch-fallback")) {
-                    Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
-                            + targetUid + ". Falling back to injecting into all windows.");
-                    return injectInputEventToTarget(event, mode, Process.INVALID_UID);
-                }
                 throw new IllegalArgumentException(
                     "Targeted input event injection from pid " + pid
                             + " was not directed at a window owned by uid "
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index dd02f23e..bbf9410 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3574,23 +3574,18 @@
             int windowFlags, @Nullable EditorInfo editorInfo,
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion,
+            int unverifiedTargetSdkVersion, @UserIdInt int userId,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
-                startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection,
-                remoteAccessibilityInputConnection, unverifiedTargetSdkVersion,
-                imeDispatcher);
-    }
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
 
-    @NonNull
-    private InputBindResult startInputOrWindowGainedFocusInternal(
-            @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
-            @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
-            int windowFlags, @Nullable EditorInfo editorInfo,
-            @Nullable IRemoteInputConnection inputConnection,
-            @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            if (editorInfo == null || editorInfo.targetInputMethodUser == null
+                    || editorInfo.targetInputMethodUser.getIdentifier() != userId) {
+                throw new InvalidParameterException("EditorInfo#targetInputMethodUser must also be "
+                        + "specified for cross-user startInputOrWindowGainedFocus()");
+            }
+        }
+
         if (windowToken == null) {
             Slog.e(TAG, "windowToken cannot be null.");
             return InputBindResult.NULL;
@@ -3600,26 +3595,6 @@
                     "IMMS.startInputOrWindowGainedFocus");
             ImeTracing.getInstance().triggerManagerServiceDump(
                     "InputMethodManagerService#startInputOrWindowGainedFocus");
-            final int callingUserId = UserHandle.getCallingUserId();
-            final int userId;
-            if (editorInfo != null && editorInfo.targetInputMethodUser != null
-                    && editorInfo.targetInputMethodUser.getIdentifier() != callingUserId) {
-                mContext.enforceCallingPermission(
-                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                        "Using EditorInfo.targetInputMethodUser requires"
-                                + " INTERACT_ACROSS_USERS_FULL.");
-                userId = editorInfo.targetInputMethodUser.getIdentifier();
-                if (!mUserManagerInternal.isUserRunning(userId)) {
-                    // There is a chance that we hit here because of race condition. Let's just
-                    // return an error code instead of crashing the caller process, which at
-                    // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
-                    // important process.
-                    Slog.e(TAG, "User #" + userId + " is not running.");
-                    return InputBindResult.INVALID_USER;
-                }
-            } else {
-                userId = callingUserId;
-            }
             final InputBindResult result;
             synchronized (ImfLock.class) {
                 final long ident = Binder.clearCallingIdentity();
@@ -3668,9 +3643,19 @@
                     + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
                     + " windowFlags=#" + Integer.toHexString(windowFlags)
                     + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+                    + " userId=" + userId
                     + " imeDispatcher=" + imeDispatcher);
         }
 
+        if (!mUserManagerInternal.isUserRunning(userId)) {
+            // There is a chance that we hit here because of race condition. Let's just
+            // return an error code instead of crashing the caller process, which at
+            // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an
+            // important process.
+            Slog.w(TAG, "User #" + userId + " is not running.");
+            return InputBindResult.INVALID_USER;
+        }
+
         final ClientState cs = mClients.get(client.asBinder());
         if (cs == null) {
             throw new IllegalArgumentException("unknown client " + client.asBinder());
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 371ef76..b06af8e 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import android.security.AndroidKeyStoreMaintenance;
+import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
@@ -210,10 +211,26 @@
                         .setBoundToSpecificSecureUserId(sid)
                         .setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
             }
+            final KeyProtection protNonRollbackResistant = builder.build();
+            builder.setRollbackResistant(true);
+            final KeyProtection protRollbackResistant = builder.build();
+            final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(keyStoreKey);
+            try {
+                keyStore.setEntry(keyAlias, entry, protRollbackResistant);
+                Slog.i(TAG, "Using rollback-resistant key");
+            } catch (KeyStoreException e) {
+                if (!(e.getCause() instanceof android.security.KeyStoreException)) {
+                    throw e;
+                }
+                int errorCode = ((android.security.KeyStoreException) e.getCause()).getErrorCode();
+                if (errorCode != KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE) {
+                    throw e;
+                }
+                Slog.w(TAG, "Rollback-resistant keys unavailable.  Falling back to "
+                        + "non-rollback-resistant key");
+                keyStore.setEntry(keyAlias, entry, protNonRollbackResistant);
+            }
 
-            keyStore.setEntry(keyAlias,
-                    new KeyStore.SecretKeyEntry(keyStoreKey),
-                    builder.build());
             byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
             return encrypt(keyStoreKey, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index c963154..4a0a07b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -38,6 +38,7 @@
 import android.app.ActivityManager.ProcessCapability;
 import android.net.NetworkPolicyManager;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
@@ -79,6 +80,8 @@
     private static final int EVENT_APP_IDLE_WL_CHANGED = 14;
     private static final int EVENT_METERED_ALLOWLIST_CHANGED = 15;
     private static final int EVENT_METERED_DENYLIST_CHANGED = 16;
+    private static final int EVENT_ROAMING_CHANGED = 17;
+    private static final int EVENT_INTERFACES_CHANGED = 18;
 
     private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
     private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -265,6 +268,24 @@
         }
     }
 
+    void roamingChanged(int netId, boolean newRoaming) {
+        synchronized (mLock) {
+            if (LOGD || mDebugUid != INVALID_UID) {
+                Slog.d(TAG, getRoamingChangedLog(netId, newRoaming));
+            }
+            mEventsBuffer.roamingChanged(netId, newRoaming);
+        }
+    }
+
+    void interfacesChanged(int netId, ArraySet<String> newIfaces) {
+        synchronized (mLock) {
+            if (LOGD || mDebugUid != INVALID_UID) {
+                Slog.d(TAG, getInterfacesChangedLog(netId, newIfaces.toString()));
+            }
+            mEventsBuffer.interfacesChanged(netId, newIfaces.toString());
+        }
+    }
+
     void setDebugUid(int uid) {
         mDebugUid = uid;
     }
@@ -348,6 +369,14 @@
         return "metered-denylist for " + uid + " changed to " + added;
     }
 
+    private static String getRoamingChangedLog(int netId, boolean newRoaming) {
+        return "Roaming of netId=" + netId + " changed to " + newRoaming;
+    }
+
+    private static String getInterfacesChangedLog(int netId, String newIfaces) {
+        return "Interfaces of netId=" + netId + " changed to " + newIfaces;
+    }
+
     private static String getFirewallChainName(int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_DOZABLE:
@@ -570,6 +599,28 @@
             data.timeStamp = System.currentTimeMillis();
         }
 
+        public void roamingChanged(int netId, boolean newRoaming) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_ROAMING_CHANGED;
+            data.ifield1 = netId;
+            data.bfield1 = newRoaming;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void interfacesChanged(int netId, String newIfaces) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_INTERFACES_CHANGED;
+            data.ifield1 = netId;
+            data.sfield1 = newIfaces;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
         public void reverseDump(IndentingPrintWriter pw) {
             final Data[] allData = toArray();
             for (int i = allData.length - 1; i >= 0; --i) {
@@ -621,6 +672,10 @@
                     return getMeteredAllowlistChangedLog(data.ifield1, data.bfield1);
                 case EVENT_METERED_DENYLIST_CHANGED:
                     return getMeteredDenylistChangedLog(data.ifield1, data.bfield1);
+                case EVENT_ROAMING_CHANGED:
+                    return getRoamingChangedLog(data.ifield1, data.bfield1);
+                case EVENT_INTERFACES_CHANGED:
+                    return getInterfacesChangedLog(data.ifield1, data.sfield1);
                 default:
                     return String.valueOf(data.type);
             }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 99a488c..de83f5a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1367,8 +1367,17 @@
                 final boolean roamingChanged = updateCapabilityChange(
                         mNetworkRoaming, newRoaming, network);
 
-                if (meteredChanged || roamingChanged) {
+                final boolean shouldUpdateNetworkRules = meteredChanged || roamingChanged;
+
+                if (meteredChanged) {
                     mLogger.meterednessChanged(network.getNetId(), newMetered);
+                }
+
+                if (roamingChanged) {
+                    mLogger.roamingChanged(network.getNetId(), newRoaming);
+                }
+
+                if (shouldUpdateNetworkRules) {
                     updateNetworkRulesNL();
                 }
             }
@@ -1381,6 +1390,7 @@
                 final boolean ifacesChanged = updateNetworkToIfacesNL(network.getNetId(),
                         newIfaces);
                 if (ifacesChanged) {
+                    mLogger.interfacesChanged(network.getNetId(), newIfaces);
                     updateNetworkRulesNL();
                 }
             }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f8604c0..2070c2b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6763,9 +6763,8 @@
 
     protected void doChannelWarningToast(int forUid, CharSequence toastText) {
         Binder.withCleanCallingIdentity(() -> {
-            final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
             final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
-                    Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, defaultWarningEnabled) != 0;
+                    Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 0) != 0;
             if (warningEnabled) {
                 Toast toast = Toast.makeText(getContext(), mHandler.getLooper(), toastText,
                         Toast.LENGTH_SHORT);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index b91f15a..748d328 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -22,7 +22,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import com.android.server.pm.pkg.SELinuxUtil;
 import android.content.pm.SigningDetails;
 import android.content.res.TypedArray;
 import android.os.Environment;
@@ -35,6 +34,7 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.pkg.SELinuxUtil;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedProvider;
@@ -255,7 +255,7 @@
     }
 
     @Override
-    public PackageImpl setSigningDetails(@Nullable SigningDetails value) {
+    public PackageImpl setSigningDetails(@NonNull SigningDetails value) {
         super.setSigningDetails(value);
         return this;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 40f859c..b7b37b2 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -334,7 +334,7 @@
 
     ParsingPackage setSharedUserLabel(int sharedUserLabel);
 
-    ParsingPackage setSigningDetails(SigningDetails signingDetails);
+    ParsingPackage setSigningDetails(@NonNull SigningDetails signingDetails);
 
     ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 6a4513d..803780f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -317,8 +317,8 @@
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
     protected String volumeUuid;
-    @Nullable
-    private SigningDetails signingDetails;
+    @NonNull
+    private SigningDetails signingDetails = SigningDetails.UNKNOWN;
 
     @NonNull
     @DataClass.ParcelWith(ForInternedString.class)
@@ -1873,7 +1873,7 @@
         return volumeUuid;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public SigningDetails getSigningDetails() {
         return signingDetails;
@@ -2474,7 +2474,7 @@
     }
 
     @Override
-    public ParsingPackageImpl setSigningDetails(@Nullable SigningDetails value) {
+    public ParsingPackageImpl setSigningDetails(@NonNull SigningDetails value) {
         signingDetails = value;
         return this;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
index 20b1ed8..2272999 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
@@ -188,6 +188,7 @@
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
      */
+    @NonNull
     SigningDetails getSigningDetails();
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6c1f3bb..69bbff3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2728,8 +2728,13 @@
         }
 
         final StartingSurfaceController.StartingSurface surface;
-        final StartingData startingData = mStartingData;
+        final boolean animate;
         if (mStartingData != null) {
+            animate = prepareAnimation && mStartingData.needRevealAnimation()
+                    && mStartingWindow.isVisibleByPolicy();
+            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
+                            + " animate=%b Callers=%s", this, mStartingWindow, animate,
+                    Debug.getCallers(5));
             surface = mStartingSurface;
             mStartingData = null;
             mStartingSurface = null;
@@ -2747,21 +2752,7 @@
             return;
         }
 
-
-        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
-                + " startingView=%s Callers=%s", this, mStartingWindow, mStartingSurface,
-                Debug.getCallers(5));
-
-        final Runnable removeSurface = () -> {
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);
-            try {
-                surface.remove(prepareAnimation && startingData.needRevealAnimation());
-            } catch (Exception e) {
-                Slog.w(TAG_WM, "Exception when removing starting window", e);
-            }
-        };
-
-        removeSurface.run();
+        surface.remove(animate);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3e62a29..4066fe1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2935,6 +2935,10 @@
                 newParent = candidateTf;
             }
         }
+        if (newParent.canHaveEmbeddingActivityTransition(mStartActivity)) {
+            // Make sure the embedded TaskFragment is included in the start activity transition.
+            newParent.collectEmbeddedTaskFragmentIfNeeded();
+        }
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3e6546e..1c20fb3 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2316,6 +2316,26 @@
         return !startBounds.equals(getBounds());
     }
 
+    boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
+        if (!isOrganizedTaskFragment() || !mTransitionController.isShellTransitionsEnabled()) {
+            return false;
+        }
+        // The activity should request open transition when it is becoming visible.
+        return child.isVisibleRequested();
+    }
+
+    void collectEmbeddedTaskFragmentIfNeeded() {
+        if (!isOrganizedTaskFragment() || mTransitionController.isCollecting(this)) {
+            return;
+        }
+        if (getChildCount() == 0) {
+            // The TaskFragment is new created, and just becoming non-empty.
+            mTransitionController.collectExistenceChange(this);
+        } else {
+            mTransitionController.collect(this);
+        }
+    }
+
     @Override
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 91f69a5..31d8eb8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1149,6 +1149,26 @@
         return false;
     }
 
+    private static boolean isTranslucent(@NonNull WindowContainer wc) {
+        final TaskFragment taskFragment = wc.asTaskFragment();
+        if (taskFragment != null) {
+            if (taskFragment.isTranslucent(null /* starting */)) {
+                return true;
+            }
+            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+            if (adjacentTaskFragment != null) {
+                // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
+                // behind two adjacent TaskFragments are occluded.
+                return adjacentTaskFragment.isTranslucent(null /* starting */);
+            }
+        }
+        // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
+        //                    it is effected by child visibility but needs to work even
+        //                    before visibility is committed. This means refactoring some
+        //                    checks to use requested visibility.
+        return !wc.fillsParent();
+    }
+
     /**
      * Under some conditions (eg. all visible targets within a parent container are transitioning
      * the same way) the transition can be "promoted" to the parent container. This means an
@@ -1701,20 +1721,13 @@
             if (mShowWallpaper || wc.showWallpaper()) {
                 flags |= FLAG_SHOW_WALLPAPER;
             }
-            if (!wc.fillsParent()) {
-                // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
-                //                    it is effected by child visibility but needs to work even
-                //                    before visibility is committed. This means refactoring some
-                //                    checks to use requested visibility.
+            if (isTranslucent(wc)) {
                 flags |= FLAG_TRANSLUCENT;
             }
             final Task task = wc.asTask();
             if (task != null && task.voiceSession != null) {
                 flags |= FLAG_IS_VOICE_INTERACTION;
             }
-            if (task != null && task.isTranslucent(null)) {
-                flags |= FLAG_TRANSLUCENT;
-            }
             final ActivityRecord record = wc.asActivityRecord();
             if (record != null) {
                 if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index d979753..0f5655c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -843,6 +844,8 @@
                     break;
                 }
 
+                prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(parent,
+                        activity);
                 activity.reparent(parent, POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
@@ -1069,6 +1072,41 @@
         return effects;
     }
 
+    private void prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(
+            @NonNull TaskFragment taskFragment, @NonNull ActivityRecord activity) {
+        if (!taskFragment.canHaveEmbeddingActivityTransition(activity)) {
+            return;
+        }
+
+        // The reparent can happen in the following cases:
+        // 1. Reparent an existing activity to split when app launches new intent.
+        //    - This happens after app calls to start activity, but before the activity is actually
+        //      started, so we don't expect any collecting transition, but if it does, we can't
+        //      queue the WCT because the start activity won't wait.
+        // 2. Reparent an existing activity to split to launch placeholder when Task size changed.
+        //    - We expect to have a collecting transition for the Task resize, so just collect.
+        // 3. Reparent a new launching activity to an always-expand container.
+        // 4. Reparent a new launching activity to split to launch placeholder together.
+        // 5. Reparent a new launching activity to an existing split.
+        //    - The new launching activity should have start an OPEN transition, so just collect.
+        // 6. Reparent PiP activity back to the original Task.
+        //    - This should be part of the exiting PiP transition, so just collect.
+
+        if (!taskFragment.getBounds().equals(activity.getBounds()) && activity.isVisible()
+                && !mTransitionController.isCollecting()) {
+            // 1. Reparent an existing activity to split when app launches new intent.
+            mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, activity);
+        }
+
+        // We expect the activity to be in the transition already, so just collect the TaskFragment.
+        if (mTransitionController.isCollecting(activity)) {
+            taskFragment.collectEmbeddedTaskFragmentIfNeeded();
+        } else {
+            ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Reparenting Activity"
+                    + " to embedded TaskFragment, but the Activity is not collected");
+        }
+    }
+
     /** A helper method to send minimum dimension violation error to the client. */
     private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
             IBinder errorCallbackToken, String reason) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3ef4aae..366ca3b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -137,7 +137,6 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.WINDOW;
@@ -4957,10 +4956,6 @@
                 || isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
-    boolean isExitAnimationRunningSelfOrChild() {
-        return isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION);
-    }
-
     private boolean shouldFinishAnimatingExit() {
         // Exit animation might be applied soon.
         if (inTransition()) {
@@ -5916,6 +5911,10 @@
         if (!super.prepareSync()) {
             return false;
         }
+        if (mIsWallpaper) {
+            // TODO(b/233286785): Add sync support to wallpaper.
+            return false;
+        }
         // In the WindowContainer implementation we immediately mark ready
         // since a generic WindowContainer only needs to wait for its
         // children to finish and is immediately ready from its own
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 721777c..13510adb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -30,6 +30,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -174,10 +175,13 @@
         when(mIrs.getConsumptionLimitLocked()).thenReturn(consumptionLimit);
 
         Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0));
+        ledger.recordTransaction(
+                new Ledger.Transaction(0, 1000L, EconomicPolicy.TYPE_REWARD | 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, 3000));
+        ledger.recordTransaction(
+                new Ledger.Transaction(0, 1000L,
+                        EconomicPolicy.TYPE_ACTION | 1, null, -5000, 3000));
         mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
         mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit);
         mScribeUnderTest.adjustRemainingConsumableCakesLocked(
@@ -209,9 +213,13 @@
     @Test
     public void testWritingPopulatedLedgerToDisk() {
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        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));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(1500, 2000,
+                        EconomicPolicy.TYPE_REWARD | 2, "green", 52, -1));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 12));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         mScribeUnderTest.loadFromDiskLocked();
@@ -230,11 +238,13 @@
                 addInstalledPackage(userId, pkgName);
                 final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
                 ledger.recordTransaction(new Ledger.Transaction(
-                        0, 1000L * u + l, 1, null, -51L * u + l, 50));
+                        0, 1000L * u + l, EconomicPolicy.TYPE_ACTION | 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, 0));
+                        1500L * u + l, 2000L * u + l,
+                        EconomicPolicy.TYPE_REWARD | 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, 0));
+                        2500L * u + l, 3000L * u + l,
+                        EconomicPolicy.TYPE_REWARD | 3 * u + l, "blue" + u + l, 3L * u + l, 0));
                 ledgers.add(userId, pkgName, ledger);
             }
         }
@@ -248,9 +258,12 @@
     @Test
     public void testDiscardLedgerFromDisk() {
         final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
-        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));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 1));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 0));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 1));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         mScribeUnderTest.loadFromDiskLocked();
@@ -269,9 +282,12 @@
     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, 1));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION | 1, null, 51, 1));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 2));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_ACTION | 3, "blue", -3, 3));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         // Package isn't installed, so make sure it's not saved to memory after loading.
@@ -283,9 +299,13 @@
     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, 0));
-        ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1));
-        ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 1));
+        ogLedger.recordTransaction(
+                new Ledger.Transaction(2500, 3000,
+                        EconomicPolicy.TYPE_REGULATION | 3, "blue", 3, 3));
         mScribeUnderTest.writeImmediatelyForTesting();
 
         // User doesn't show up with any packages, so make sure nothing is saved after loading.
@@ -331,12 +351,34 @@
         }
         assertNotNull(actual);
         assertEquals(expected.getCurrentBalance(), actual.getCurrentBalance());
+
         List<Ledger.Transaction> expectedTransactions = expected.getTransactions();
         List<Ledger.Transaction> actualTransactions = actual.getTransactions();
         assertEquals(expectedTransactions.size(), actualTransactions.size());
         for (int i = 0; i < expectedTransactions.size(); ++i) {
             assertTransactionsEqual(expectedTransactions.get(i), actualTransactions.get(i));
         }
+
+        List<Ledger.RewardBucket> expectedRewardBuckets = expected.getRewardBuckets();
+        List<Ledger.RewardBucket> actualRewardBuckets = actual.getRewardBuckets();
+        assertEquals(expectedRewardBuckets.size(), actualRewardBuckets.size());
+        for (int i = 0; i < expectedRewardBuckets.size(); ++i) {
+            assertRewardBucketsEqual(expectedRewardBuckets.get(i), actualRewardBuckets.get(i));
+        }
+    }
+
+    private void assertSparseLongArraysEqual(SparseLongArray expected, SparseLongArray actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        final int size = expected.size();
+        assertEquals(size, actual.size());
+        for (int i = 0; i < size; ++i) {
+            assertEquals(expected.keyAt(i), actual.keyAt(i));
+            assertEquals(expected.valueAt(i), actual.valueAt(i));
+        }
     }
 
     private void assertReportListsEqual(List<Analyst.Report> expected,
@@ -382,6 +424,17 @@
         }
     }
 
+    private void assertRewardBucketsEqual(Ledger.RewardBucket expected,
+            Ledger.RewardBucket actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        assertEquals(expected.startTimeMs, actual.startTimeMs);
+        assertSparseLongArraysEqual(expected.cumulativeDelta, actual.cumulativeDelta);
+    }
+
     private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
         if (expected == null) {
             assertNull(actual);
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 22dcf84..54566c3 100644
--- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
@@ -22,7 +22,12 @@
 import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -32,7 +37,10 @@
 import org.junit.runner.RunWith;
 
 import java.time.Clock;
+import java.time.Duration;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
 
 /** Test that the ledger records transactions correctly. */
 @RunWith(AndroidJUnit4.class)
@@ -44,6 +52,11 @@
         TareUtils.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
     }
 
+    private void shiftSystemTime(long incrementMs) {
+        TareUtils.sSystemClock =
+                Clock.offset(TareUtils.sSystemClock, Duration.ofMillis(incrementMs));
+    }
+
     @Test
     public void testInitialState() {
         final Ledger ledger = new Ledger();
@@ -52,37 +65,149 @@
     }
 
     @Test
+    public void testInitialization_FullLists() {
+        final long balance = 1234567890L;
+        List<Ledger.Transaction> transactions = new ArrayList<>();
+        List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
+
+        final long now = getCurrentTimeMillis();
+        Ledger.Transaction secondTxn = null;
+        Ledger.RewardBucket remainingBucket = null;
+        for (int i = 0; i < Ledger.MAX_TRANSACTION_COUNT; ++i) {
+            final long start = now - 10 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+            Ledger.Transaction transaction = new Ledger.Transaction(
+                    start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+            if (i == 1) {
+                secondTxn = transaction;
+            }
+            transactions.add(transaction);
+        }
+        for (int b = 0; b < Ledger.NUM_REWARD_BUCKET_WINDOWS; ++b) {
+            final long start = now - (Ledger.NUM_REWARD_BUCKET_WINDOWS - b) * 24 * HOUR_IN_MILLIS;
+            Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+            rewardBucket.startTimeMs = start;
+            for (int r = 0; r < 5; ++r) {
+                rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | r, b * start + r);
+            }
+            if (b == Ledger.NUM_REWARD_BUCKET_WINDOWS - 1) {
+                remainingBucket = rewardBucket;
+            }
+            rewardBuckets.add(rewardBucket);
+        }
+        final Ledger ledger = new Ledger(balance, transactions, rewardBuckets);
+        assertEquals(balance, ledger.getCurrentBalance());
+        assertEquals(transactions, ledger.getTransactions());
+        // Everything but the last bucket is old, so the returned list should only contain that
+        // bucket.
+        rewardBuckets.clear();
+        rewardBuckets.add(remainingBucket);
+        assertEquals(rewardBuckets, ledger.getRewardBuckets());
+
+        // Make sure the ledger can properly record new transactions.
+        final long start = now - MINUTE_IN_MILLIS;
+        final long delta = 400;
+        final Ledger.Transaction transaction = new Ledger.Transaction(
+                start, start + MINUTE_IN_MILLIS, EconomicPolicy.TYPE_REWARD | 1, null, delta, 0);
+        ledger.recordTransaction(transaction);
+        assertEquals(balance + delta, ledger.getCurrentBalance());
+        transactions = ledger.getTransactions();
+        assertEquals(secondTxn, transactions.get(0));
+        assertEquals(transaction, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 1));
+        final Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+        rewardBucket.startTimeMs = now;
+        rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | 1, delta);
+        rewardBuckets = ledger.getRewardBuckets();
+        assertRewardBucketsEqual(remainingBucket, rewardBuckets.get(0));
+        assertRewardBucketsEqual(rewardBucket, rewardBuckets.get(1));
+    }
+
+    @Test
+    public void testInitialization_OverflowingLists() {
+        final long balance = 1234567890L;
+        final List<Ledger.Transaction> transactions = new ArrayList<>();
+        final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
+
+        final long now = getCurrentTimeMillis();
+        for (int i = 0; i < 2 * Ledger.MAX_TRANSACTION_COUNT; ++i) {
+            final long start = now - 20 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+            Ledger.Transaction transaction = new Ledger.Transaction(
+                    start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+            transactions.add(transaction);
+        }
+        for (int b = 0; b < 2 * Ledger.NUM_REWARD_BUCKET_WINDOWS; ++b) {
+            final long start = now
+                    - (2 * Ledger.NUM_REWARD_BUCKET_WINDOWS - b) * 6 * HOUR_IN_MILLIS;
+            Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+            rewardBucket.startTimeMs = start;
+            for (int r = 0; r < 5; ++r) {
+                rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | r, b * start + r);
+            }
+            rewardBuckets.add(rewardBucket);
+        }
+        final Ledger ledger = new Ledger(balance, transactions, rewardBuckets);
+        assertEquals(balance, ledger.getCurrentBalance());
+        assertEquals(transactions.subList(Ledger.MAX_TRANSACTION_COUNT,
+                        2 * Ledger.MAX_TRANSACTION_COUNT),
+                ledger.getTransactions());
+        assertEquals(rewardBuckets.subList(Ledger.NUM_REWARD_BUCKET_WINDOWS,
+                        2 * Ledger.NUM_REWARD_BUCKET_WINDOWS),
+                ledger.getRewardBuckets());
+    }
+
+    @Test
     public void testMultipleTransactions() {
         final Ledger ledger = new Ledger();
         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, 0));
         assertEquals(30, ledger.getCurrentBalance());
-        assertEquals(30, ledger.get24HourSum(1, 60_000));
         ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5));
         assertEquals(20, ledger.getCurrentBalance());
-        assertEquals(20, ledger.get24HourSum(1, 60_000));
     }
 
     @Test
     public void test24HourSum() {
+        final long now = getCurrentTimeMillis();
+        final long end = now + 24 * HOUR_IN_MILLIS;
+        final int reward1 = EconomicPolicy.TYPE_REWARD | 1;
+        final int reward2 = EconomicPolicy.TYPE_REWARD | 2;
         final Ledger ledger = new Ledger();
-        ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0));
-        assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
+
+        // First bucket
+        assertEquals(0, ledger.get24HourSum(reward1, end));
+        ledger.recordTransaction(new Ledger.Transaction(now, now + 1000, reward1, null, 500, 0));
+        assertEquals(500, ledger.get24HourSum(reward1, end));
+        assertEquals(0, ledger.get24HourSum(reward2, end));
         ledger.recordTransaction(
-                new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0));
-        assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
+                new Ledger.Transaction(now + 2 * HOUR_IN_MILLIS, now + 3 * HOUR_IN_MILLIS,
+                        reward1, null, 2500, 0));
+        assertEquals(3000, ledger.get24HourSum(reward1, end));
+        // Second bucket
+        shiftSystemTime(7 * HOUR_IN_MILLIS); // now + 7
         ledger.recordTransaction(
-                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));
-        // Pro-rated as the second transaction phases out
-        assertEquals(1251,
-                ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS + 30 * MINUTE_IN_MILLIS));
-        assertEquals(1, ledger.get24HourSum(1, 27 * HOUR_IN_MILLIS));
-        assertEquals(0, ledger.get24HourSum(1, 28 * HOUR_IN_MILLIS));
+                new Ledger.Transaction(now + 7 * HOUR_IN_MILLIS, now + 7 * HOUR_IN_MILLIS,
+                        reward1, null, 1, 0));
+        ledger.recordTransaction(
+                new Ledger.Transaction(now + 7 * HOUR_IN_MILLIS, now + 7 * HOUR_IN_MILLIS,
+                        reward2, null, 42, 0));
+        assertEquals(3001, ledger.get24HourSum(reward1, end));
+        assertEquals(42, ledger.get24HourSum(reward2, end));
+        // Third bucket
+        shiftSystemTime(12 * HOUR_IN_MILLIS); // now + 19
+        ledger.recordTransaction(
+                new Ledger.Transaction(now + 12 * HOUR_IN_MILLIS, now + 13 * HOUR_IN_MILLIS,
+                        reward1, null, 300, 0));
+        assertEquals(3301, ledger.get24HourSum(reward1, end));
+        assertRewardBucketsInOrder(ledger.getRewardBuckets());
+        // Older buckets should be excluded
+        assertEquals(301, ledger.get24HourSum(reward1, end + HOUR_IN_MILLIS));
+        assertEquals(301, ledger.get24HourSum(reward1, end + 2 * HOUR_IN_MILLIS));
+        // 2nd bucket should still be included since it started at the 7 hour mark
+        assertEquals(301, ledger.get24HourSum(reward1, end + 6 * HOUR_IN_MILLIS));
+        assertEquals(42, ledger.get24HourSum(reward2, end + 6 * HOUR_IN_MILLIS));
+        assertEquals(300, ledger.get24HourSum(reward1, end + 7 * HOUR_IN_MILLIS + 1));
+        assertEquals(0, ledger.get24HourSum(reward2, end + 8 * HOUR_IN_MILLIS));
+        assertEquals(0, ledger.get24HourSum(reward1, end + 19 * HOUR_IN_MILLIS + 1));
     }
 
     @Test
@@ -125,4 +250,127 @@
         ledger.removeOldTransactions(0);
         assertNull(ledger.getEarliestTransaction());
     }
+
+    @Test
+    public void testTransactionsAlwaysInOrder() {
+        final Ledger ledger = new Ledger();
+        List<Ledger.Transaction> transactions = ledger.getTransactions();
+        assertTrue(transactions.isEmpty());
+
+        final long now = getCurrentTimeMillis();
+        Ledger.Transaction transaction1 = new Ledger.Transaction(
+                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, 0);
+        Ledger.Transaction transaction3 = new Ledger.Transaction(
+                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, 0);
+
+        Ledger.Transaction transaction5 = new Ledger.Transaction(
+                now - 15 * HOUR_IN_MILLIS, now - 15 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS,
+                1, null, 400, 0);
+        ledger.recordTransaction(transaction1);
+        ledger.recordTransaction(transaction2);
+        ledger.recordTransaction(transaction3);
+        ledger.recordTransaction(transaction4);
+        ledger.recordTransaction(transaction5);
+
+        transactions = ledger.getTransactions();
+        assertEquals(5, transactions.size());
+        assertTransactionsInOrder(transactions);
+
+        for (int i = 0; i < Ledger.MAX_TRANSACTION_COUNT - 5; ++i) {
+            final long start = now - 10 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+            Ledger.Transaction transaction = new Ledger.Transaction(
+                    start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+            ledger.recordTransaction(transaction);
+        }
+        transactions = ledger.getTransactions();
+        assertEquals(Ledger.MAX_TRANSACTION_COUNT, transactions.size());
+        assertTransactionsInOrder(transactions);
+
+        long start = now - 5 * HOUR_IN_MILLIS;
+        Ledger.Transaction transactionLast5 = new Ledger.Transaction(
+                start, start + MINUTE_IN_MILLIS, 1, null, 4800, 0);
+        start = now - 4 * HOUR_IN_MILLIS;
+        Ledger.Transaction transactionLast4 = new Ledger.Transaction(
+                start, start + MINUTE_IN_MILLIS, 1, null, 600, 0);
+        start = now - 3 * HOUR_IN_MILLIS;
+        Ledger.Transaction transactionLast3 = new Ledger.Transaction(
+                start, start + MINUTE_IN_MILLIS, 1, null, 600, 0);
+        // Instant event
+        start = now - 2 * HOUR_IN_MILLIS;
+        Ledger.Transaction transactionLast2 = new Ledger.Transaction(
+                start, start, 1, null, 500, 0);
+        Ledger.Transaction transactionLast1 = new Ledger.Transaction(
+                start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+        ledger.recordTransaction(transactionLast5);
+        ledger.recordTransaction(transactionLast4);
+        ledger.recordTransaction(transactionLast3);
+        ledger.recordTransaction(transactionLast2);
+        ledger.recordTransaction(transactionLast1);
+
+        transactions = ledger.getTransactions();
+        assertEquals(Ledger.MAX_TRANSACTION_COUNT, transactions.size());
+        assertTransactionsInOrder(transactions);
+        assertEquals(transactionLast1, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 1));
+        assertEquals(transactionLast2, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 2));
+        assertEquals(transactionLast3, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 3));
+        assertEquals(transactionLast4, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 4));
+        assertEquals(transactionLast5, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 5));
+        assertFalse(transactions.contains(transaction1));
+        assertFalse(transactions.contains(transaction2));
+        assertFalse(transactions.contains(transaction3));
+        assertFalse(transactions.contains(transaction4));
+        assertFalse(transactions.contains(transaction5));
+    }
+
+    private void assertSparseLongArraysEqual(SparseLongArray expected, SparseLongArray actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        final int size = expected.size();
+        assertEquals(size, actual.size());
+        for (int i = 0; i < size; ++i) {
+            assertEquals(expected.keyAt(i), actual.keyAt(i));
+            assertEquals(expected.valueAt(i), actual.valueAt(i));
+        }
+    }
+
+    private void assertRewardBucketsEqual(Ledger.RewardBucket expected,
+            Ledger.RewardBucket actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        assertEquals(expected.startTimeMs, actual.startTimeMs);
+        assertSparseLongArraysEqual(expected.cumulativeDelta, actual.cumulativeDelta);
+    }
+
+    private void assertRewardBucketsInOrder(List<Ledger.RewardBucket> rewardBuckets) {
+        assertNotNull(rewardBuckets);
+        for (int i = 1; i < rewardBuckets.size(); ++i) {
+            final Ledger.RewardBucket prev = rewardBuckets.get(i - 1);
+            final Ledger.RewardBucket cur = rewardBuckets.get(i);
+            assertTrue("Newer bucket stored before older bucket @ index " + i
+                            + ": " + prev.startTimeMs + " vs " + cur.startTimeMs,
+                    prev.startTimeMs <= cur.startTimeMs);
+        }
+    }
+
+    private void assertTransactionsInOrder(List<Ledger.Transaction> transactions) {
+        assertNotNull(transactions);
+        for (int i = 1; i < transactions.size(); ++i) {
+            final Ledger.Transaction prev = transactions.get(i - 1);
+            final Ledger.Transaction cur = transactions.get(i);
+            assertTrue("Newer transaction stored before older transaction @ index " + i
+                            + ": " + prev.endTimeMs + " vs " + cur.endTimeMs,
+                    prev.endTimeMs <= cur.endTimeMs);
+        }
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 60a159154..4640d36 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -88,6 +88,12 @@
     @Test
     override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 240238245)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 386c0cb..a9fb0f2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -83,6 +83,7 @@
         transitions {
             tapl.launchedAppState.quickSwitchToPreviousApp()
             wmHelper.StateSyncBuilder()
+                .withFullScreenApp(testApp1)
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
                 .waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index cefcb35..3b60212 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -79,6 +79,7 @@
                 testApp2.launchViaIntent(wmHelper)
                 tapl.launchedAppState.quickSwitchToPreviousApp()
                 wmHelper.StateSyncBuilder()
+                    .withFullScreenApp(testApp1)
                     .withNavOrTaskBarVisible()
                     .withStatusBarVisible()
                     .waitForAndVerify()
@@ -89,6 +90,7 @@
         transitions {
             tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
             wmHelper.StateSyncBuilder()
+                .withFullScreenApp(testApp2)
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
                 .waitForAndVerify()