Merge "Add identityHashCode to App List item key"
diff --git a/Android.bp b/Android.bp
index cfab18e..ea7ced9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,6 +224,7 @@
         "android.hardware.radio.messaging-V2-java",
         "android.hardware.radio.modem-V2-java",
         "android.hardware.radio.network-V2-java",
+        "android.hardware.radio.satellite-V1-java",
         "android.hardware.radio.sim-V2-java",
         "android.hardware.radio.voice-V2-java",
         "android.hardware.thermal-V1.0-java-constants",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index bf8984f..62d97358 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -158,33 +158,37 @@
      * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
      */
     static final int WORK_TYPE_FGS = 1 << 1;
+    /** The job is allowed to run as a user-initiated job for a currently active user. */
+    static final int WORK_TYPE_UI = 1 << 2;
     /** The job is allowed to run as an expedited job for a currently active user. */
-    static final int WORK_TYPE_EJ = 1 << 2;
+    static final int WORK_TYPE_EJ = 1 << 3;
     /**
      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
      * can run as a background job.
      */
-    static final int WORK_TYPE_BG = 1 << 3;
+    static final int WORK_TYPE_BG = 1 << 4;
     /**
      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
-     * state, or is allowed to run as an expedited job, but is for a completely background user.
+     * state, or is allowed to run as an expedited or user-initiated job,
+     * but is for a completely background user.
      */
-    static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4;
+    static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 5;
     /**
      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
      * so can run as a background user job.
      */
-    static final int WORK_TYPE_BGUSER = 1 << 5;
+    static final int WORK_TYPE_BGUSER = 1 << 6;
     @VisibleForTesting
-    static final int NUM_WORK_TYPES = 6;
+    static final int NUM_WORK_TYPES = 7;
     private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
 
     @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
             WORK_TYPE_NONE,
             WORK_TYPE_TOP,
             WORK_TYPE_FGS,
+            WORK_TYPE_UI,
             WORK_TYPE_EJ,
             WORK_TYPE_BG,
             WORK_TYPE_BGUSER_IMPORTANT,
@@ -203,6 +207,8 @@
                 return "TOP";
             case WORK_TYPE_FGS:
                 return "FGS";
+            case WORK_TYPE_UI:
+                return "UI";
             case WORK_TYPE_EJ:
                 return "EJ";
             case WORK_TYPE_BG:
@@ -238,8 +244,9 @@
                             // defaultMin
                             List.of(Pair.create(WORK_TYPE_TOP, .4f),
                                     Pair.create(WORK_TYPE_FGS, .2f),
-                                    Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f),
-                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+                                    Pair.create(WORK_TYPE_UI, .1f),
+                                    Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, .5f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .25f),
@@ -250,6 +257,7 @@
                             // defaultMin
                             List.of(Pair.create(WORK_TYPE_TOP, .4f),
                                     Pair.create(WORK_TYPE_FGS, .1f),
+                                    Pair.create(WORK_TYPE_UI, .1f),
                                     Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .1f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
                             // defaultMax
@@ -260,8 +268,9 @@
                     new WorkTypeConfig("screen_on_low", DEFAULT_CONCURRENCY_LIMIT,
                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+                            List.of(Pair.create(WORK_TYPE_TOP, .6f),
                                     Pair.create(WORK_TYPE_FGS, .1f),
+                                    Pair.create(WORK_TYPE_UI, .1f),
                                     Pair.create(WORK_TYPE_EJ, .1f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1.0f / 3),
@@ -271,9 +280,10 @@
                     new WorkTypeConfig("screen_on_critical", DEFAULT_CONCURRENCY_LIMIT,
                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+                            List.of(Pair.create(WORK_TYPE_TOP, .7f),
                                     Pair.create(WORK_TYPE_FGS, .1f),
-                                    Pair.create(WORK_TYPE_EJ, .1f)),
+                                    Pair.create(WORK_TYPE_UI, .1f),
+                                    Pair.create(WORK_TYPE_EJ, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, 1.0f / 6),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 6),
@@ -287,8 +297,9 @@
                             // defaultMin
                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
                                     Pair.create(WORK_TYPE_FGS, .2f),
-                                    Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
-                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+                                    Pair.create(WORK_TYPE_UI, .2f),
+                                    Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, .6f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .2f),
@@ -299,8 +310,9 @@
                             // defaultMin
                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
                                     Pair.create(WORK_TYPE_FGS, .2f),
-                                    Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
-                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+                                    Pair.create(WORK_TYPE_UI, .2f),
+                                    Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, .5f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -309,9 +321,11 @@
                     new WorkTypeConfig("screen_off_low", DEFAULT_CONCURRENCY_LIMIT,
                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 6 / 10,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, .4f),
-                                    Pair.create(WORK_TYPE_FGS, .1f),
-                                    Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f)),
+                            List.of(Pair.create(WORK_TYPE_TOP, .3f),
+                                    Pair.create(WORK_TYPE_FGS, .15f),
+                                    Pair.create(WORK_TYPE_UI, .15f),
+                                    Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+                                    Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, .25f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -320,9 +334,10 @@
                     new WorkTypeConfig("screen_off_critical", DEFAULT_CONCURRENCY_LIMIT,
                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
                             // defaultMin
-                            List.of(Pair.create(WORK_TYPE_TOP, .5f),
+                            List.of(Pair.create(WORK_TYPE_TOP, .3f),
                                     Pair.create(WORK_TYPE_FGS, .1f),
-                                    Pair.create(WORK_TYPE_EJ, .1f)),
+                                    Pair.create(WORK_TYPE_UI, .1f),
+                                    Pair.create(WORK_TYPE_EJ, .05f)),
                             // defaultMax
                             List.of(Pair.create(WORK_TYPE_BG, .1f),
                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -2097,10 +2112,12 @@
 
             if (js.shouldTreatAsExpeditedJob()) {
                 classification |= WORK_TYPE_EJ;
+            } else if (js.shouldTreatAsUserInitiatedJob()) {
+                classification |= WORK_TYPE_UI;
             }
         } else {
             if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
-                    || js.shouldTreatAsExpeditedJob()) {
+                    || js.shouldTreatAsExpeditedJob() || js.shouldTreatAsUserInitiatedJob()) {
                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
             }
             // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
@@ -2120,6 +2137,7 @@
         static final String KEY_PREFIX_MAX_RATIO = KEY_PREFIX_MAX + "ratio_";
         private static final String KEY_PREFIX_MAX_RATIO_TOP = KEY_PREFIX_MAX_RATIO + "top_";
         private static final String KEY_PREFIX_MAX_RATIO_FGS = KEY_PREFIX_MAX_RATIO + "fgs_";
+        private static final String KEY_PREFIX_MAX_RATIO_UI = KEY_PREFIX_MAX_RATIO + "ui_";
         private static final String KEY_PREFIX_MAX_RATIO_EJ = KEY_PREFIX_MAX_RATIO + "ej_";
         private static final String KEY_PREFIX_MAX_RATIO_BG = KEY_PREFIX_MAX_RATIO + "bg_";
         private static final String KEY_PREFIX_MAX_RATIO_BGUSER = KEY_PREFIX_MAX_RATIO + "bguser_";
@@ -2129,6 +2147,7 @@
         static final String KEY_PREFIX_MIN_RATIO = KEY_PREFIX_MIN + "ratio_";
         private static final String KEY_PREFIX_MIN_RATIO_TOP = KEY_PREFIX_MIN_RATIO + "top_";
         private static final String KEY_PREFIX_MIN_RATIO_FGS = KEY_PREFIX_MIN_RATIO + "fgs_";
+        private static final String KEY_PREFIX_MIN_RATIO_UI = KEY_PREFIX_MIN_RATIO + "ui_";
         private static final String KEY_PREFIX_MIN_RATIO_EJ = KEY_PREFIX_MIN_RATIO + "ej_";
         private static final String KEY_PREFIX_MIN_RATIO_BG = KEY_PREFIX_MIN_RATIO + "bg_";
         private static final String KEY_PREFIX_MIN_RATIO_BGUSER = KEY_PREFIX_MIN_RATIO + "bguser_";
@@ -2209,6 +2228,9 @@
             final int maxFgs = getMaxValue(properties,
                     KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS, oneIntBits);
             mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
+            final int maxUi = getMaxValue(properties,
+                    KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI, oneIntBits);
+            mMaxAllowedSlots.put(WORK_TYPE_UI, maxUi);
             final int maxEj = getMaxValue(properties,
                     KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ, oneIntBits);
             mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
@@ -2237,6 +2259,12 @@
                     0, Math.min(maxFgs, remaining));
             mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
             remaining -= minFgs;
+            // Ensure ui is in the range [0, min(maxUi, remaining)]
+            final int minUi = getMinValue(properties,
+                    KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI,
+                    0, Math.min(maxUi, remaining));
+            mMinReservedSlots.put(WORK_TYPE_UI, minUi);
+            remaining -= minUi;
             // Ensure ej is in the range [0, min(maxEj, remaining)]
             final int minEj = getMinValue(properties,
                     KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ,
@@ -2313,6 +2341,12 @@
             pw.print(KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier,
                             mMaxAllowedSlots.get(WORK_TYPE_FGS))
                     .println();
+            pw.print(KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier,
+                            mMinReservedSlots.get(WORK_TYPE_UI))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier,
+                            mMaxAllowedSlots.get(WORK_TYPE_UI))
+                    .println();
             pw.print(KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier,
                             mMinReservedSlots.get(WORK_TYPE_EJ))
                     .println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
index f27da4a..06333f1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -408,7 +408,7 @@
             if (report.screenOffDurationMs > 0) {
                 pw.print(padStringWithSpaces(String.format("%d mAh (%.2f%%/hr)",
                                 report.screenOffDischargeMah,
-                                1.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
+                                100.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
                                         / (batteryCapacityMah * report.screenOffDurationMs)),
                         statColsLength));
             } else {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 4001d9b..08c1a0c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -209,8 +209,10 @@
     @GuardedBy("mLock")
     private int mCurrentBatteryLevel;
 
-    // TODO(250007395): make configurable per device
-    private final int mTargetBackgroundBatteryLifeHours;
+    // TODO(250007395): make configurable per device (via config.xml)
+    private final int mDefaultTargetBackgroundBatteryLifeHours;
+    @GuardedBy("mLock")
+    private int mTargetBackgroundBatteryLifeHours;
 
     private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
         @Override
@@ -353,10 +355,11 @@
 
         mConfigObserver = new ConfigObserver(mHandler, context);
 
-        mTargetBackgroundBatteryLifeHours =
+        mDefaultTargetBackgroundBatteryLifeHours =
                 mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
-                        ? 200 // ~ 0.5%/hr
-                        : 100; // ~ 1%/hr
+                        ? 100 // ~ 1.0%/hr
+                        : 40; // ~ 2.5%/hr
+        mTargetBackgroundBatteryLifeHours = mDefaultTargetBackgroundBatteryLifeHours;
 
         publishLocalService(EconomyManagerInternal.class, new LocalService());
     }
@@ -1483,6 +1486,8 @@
     private class ConfigObserver extends ContentObserver
             implements DeviceConfig.OnPropertiesChangedListener {
         private static final String KEY_ENABLE_TIP3 = "enable_tip3";
+        private static final String KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS =
+                "target_bg_battery_life_hrs";
 
         private static final boolean DEFAULT_ENABLE_TIP3 = true;
 
@@ -1541,6 +1546,13 @@
                         case KEY_ENABLE_TIP3:
                             ENABLE_TIP3 = properties.getBoolean(name, DEFAULT_ENABLE_TIP3);
                             break;
+                        case KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS:
+                            synchronized (mLock) {
+                                mTargetBackgroundBatteryLifeHours = properties.getInt(name,
+                                        mDefaultTargetBackgroundBatteryLifeHours);
+                                maybeAdjustDesiredStockLevelLocked();
+                            }
+                            break;
                         default:
                             if (!economicPolicyUpdated
                                     && (name.startsWith("am") || name.startsWith("js")
@@ -1670,6 +1682,12 @@
             pw.print("/");
             pw.println(cakeToString(mScribe.getSatiatedConsumptionLimitLocked()));
 
+            pw.print("Target bg battery life (hours): ");
+            pw.print(mTargetBackgroundBatteryLifeHours);
+            pw.print(" (");
+            pw.print(String.format("%.2f", 100f / mTargetBackgroundBatteryLifeHours));
+            pw.println("%/hr)");
+
             final long remainingConsumable = mScribe.getRemainingConsumableCakesLocked();
             pw.print("Goods remaining: ");
             pw.print(cakeToString(remainingConsumable));
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 b41c0d1..66327fd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -717,8 +717,8 @@
         out.startTag(null, XML_TAG_USER);
         out.attributeInt(null, XML_ATTR_USER_ID, userId);
         out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
-                mRealtimeSinceUsersAddedOffsets.get(userId,
-                        mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
+                mRealtimeSinceUsersAddedOffsets.get(userId, mLoadedTimeSinceFirstSetup)
+                        + SystemClock.elapsedRealtime());
         for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
             final String pkgName = mLedgers.keyAt(uIdx, pIdx);
             final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/core/api/current.txt b/core/api/current.txt
index 9e4c958..d0a2e91 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13318,7 +13318,7 @@
     method @NonNull public android.os.Bundle getCandidateQueryData();
     method @NonNull public android.os.Bundle getCredentialData();
     method @NonNull public String getType();
-    method public boolean requireSystemProvider();
+    method public boolean isSystemProviderRequired();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
   }
@@ -13343,8 +13343,8 @@
 
   public final class CredentialManager {
     method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
-    method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
-    method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+    method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+    method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
   }
 
   public class GetCredentialException extends java.lang.Exception {
@@ -13365,7 +13365,7 @@
     method @NonNull public android.os.Bundle getCandidateQueryData();
     method @NonNull public android.os.Bundle getCredentialRetrievalData();
     method @NonNull public String getType();
-    method public boolean requireSystemProvider();
+    method public boolean isSystemProviderRequired();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
   }
@@ -19278,7 +19278,16 @@
 
 package android.hardware.input {
 
+  public final class HostUsiVersion implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getMajorVersion();
+    method public int getMinorVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.HostUsiVersion> CREATOR;
+  }
+
   public final class InputManager {
+    method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
     method @Nullable public android.view.InputDevice getInputDevice(int);
     method public int[] getInputDeviceIds();
     method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
@@ -24051,6 +24060,7 @@
     method @Nullable public android.os.Bundle getControlHints();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getDeselectableRoutes();
     method @NonNull public String getId();
+    method @NonNull public android.media.RoutingSessionInfo getRoutingSessionInfo();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
     method public int getVolume();
@@ -40432,17 +40442,24 @@
     method @NonNull public String getCardId();
     method @NonNull public android.graphics.drawable.Icon getCardImage();
     method @Nullable public CharSequence getCardLabel();
+    method @NonNull public int getCardType();
     method @NonNull public CharSequence getContentDescription();
     method @NonNull public android.app.PendingIntent getPendingIntent();
+    method @Nullable public android.graphics.drawable.Icon getValuableCardSecondaryImage();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CARD_TYPE_PAYMENT = 1; // 0x1
+    field public static final int CARD_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int CARD_TYPE_VALUABLE = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletCard> CREATOR;
   }
 
   public static final class WalletCard.Builder {
+    ctor public WalletCard.Builder(@NonNull String, @NonNull int, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
     ctor public WalletCard.Builder(@NonNull String, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
     method @NonNull public android.service.quickaccesswallet.WalletCard build();
     method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+    method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setValuableCardSecondaryImage(@Nullable android.graphics.drawable.Icon);
   }
 
   public final class WalletServiceEvent implements android.os.Parcelable {
@@ -40563,7 +40580,6 @@
     method public void setDisabledShowContext(int);
     method public final void setUiHints(@NonNull android.os.Bundle);
     method public void showSession(android.os.Bundle, int);
-    field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
     field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
     field public static final String SERVICE_META_DATA = "android.voice_interaction";
   }
@@ -40624,6 +40640,7 @@
     method public void startAssistantActivity(android.content.Intent);
     method public void startVoiceActivity(android.content.Intent);
     method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
+    field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
     field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
     field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
     field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -41362,8 +41379,7 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
-  public final class CallControl implements java.lang.AutoCloseable {
-    method public void close();
+  public final class CallControl {
     method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
     method @NonNull public android.os.ParcelUuid getCallId();
     method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -54248,6 +54264,7 @@
     method public void notifyViewExited(@NonNull android.view.View, int);
     method public void notifyViewVisibilityChanged(@NonNull android.view.View, boolean);
     method public void notifyViewVisibilityChanged(@NonNull android.view.View, int, boolean);
+    method public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
@@ -54294,6 +54311,16 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR;
   }
 
+  public final class VirtualViewFillInfo {
+    method @Nullable public String[] getAutofillHints();
+  }
+
+  public static final class VirtualViewFillInfo.Builder {
+    ctor public VirtualViewFillInfo.Builder();
+    method @NonNull public android.view.autofill.VirtualViewFillInfo build();
+    method @NonNull public android.view.autofill.VirtualViewFillInfo.Builder setAutofillHints(@NonNull java.lang.String...);
+  }
+
 }
 
 package android.view.contentcapture {
@@ -55020,6 +55047,22 @@
     method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String);
   }
 
+  public final class InsertModeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.os.CancellationSignal getCancellationSignal();
+    method @NonNull public android.graphics.PointF getInsertionPoint();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertModeGesture> CREATOR;
+  }
+
+  public static final class InsertModeGesture.Builder {
+    ctor public InsertModeGesture.Builder();
+    method @NonNull public android.view.inputmethod.InsertModeGesture build();
+    method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setCancellationSignal(@NonNull android.os.CancellationSignal);
+    method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setFallbackText(@Nullable String);
+    method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF);
+  }
+
   public final class JoinOrSplitGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.PointF getJoinOrSplitPoint();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b70049b..5183a82 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -91,6 +91,7 @@
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+    field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
     field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
     field public static final String CAPTURE_TUNER_AUDIO_INPUT = "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
     field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
@@ -582,6 +583,7 @@
     field public static final String OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER = "android:auto_revoke_managed_by_installer";
     field public static final String OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = "android:auto_revoke_permissions_if_unused";
     field public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "android:bind_accessibility_service";
+    field public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android:capture_consentless_bugreport_on_userdebug_build";
     field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
     field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
     field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service";
@@ -3008,8 +3010,13 @@
     method public void onIntentIntercepted(@NonNull android.content.Intent);
   }
 
+  public static interface VirtualDeviceManager.SoundEffectListener {
+    method public void onPlaySoundEffect(int);
+  }
+
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+    method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull public android.content.Context createContext();
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
@@ -3028,6 +3035,7 @@
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+    method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -10045,6 +10053,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
     method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
     method public void unregisterSection(int);
+    field public static final int FLAG_ALLOW_CONSENTLESS_BUGREPORT = 2; // 0x2
     field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
     field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
     field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8c64e40..94075e5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -908,8 +908,6 @@
     method public boolean isProfile();
     method public boolean isQuietModeEnabled();
     method public boolean isRestricted();
-    method public boolean isSystemOnly();
-    method public static boolean isSystemOnly(int);
     method public boolean supportsSwitchTo();
     method public boolean supportsSwitchToByUser();
     method public void writeToParcel(android.os.Parcel, int);
@@ -2032,7 +2030,6 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
-    method public static boolean isSplitSystemUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
     method public boolean isVisibleBackgroundUsersSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
@@ -2796,6 +2793,7 @@
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
     field public static final int HAL_SERVICE_MODEM = 3; // 0x3
     field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+    field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
     field public static final int HAL_SERVICE_SIM = 5; // 0x5
     field public static final int HAL_SERVICE_VOICE = 6; // 0x6
     field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3338,6 +3336,7 @@
     field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
     field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
     field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
+    field public static final int GESTURE_TYPE_INSERT_MODE = 128; // 0x80
     field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10
     field public static final int GESTURE_TYPE_NONE = 0; // 0x0
     field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a9d14df..a81ef18 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ConstantState;
 import android.os.Build;
+import android.util.LongArray;
 
 import java.util.ArrayList;
 
@@ -546,7 +547,6 @@
      */
     void skipToEndValue(boolean inReverse) {}
 
-
     /**
      * Internal use only.
      *
@@ -559,9 +559,36 @@
     }
 
     /**
-     * Internal use only.
+     * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+     * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+     * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+     * done between the two times.
      */
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+    /**
+     * Internal use only. This animates any animation that has ended since lastPlayTime.
+     * If an animation hasn't been finished, no change will be made.
+     */
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+    /**
+     * Internal use only. Adds all start times (after delay) to and end times to times.
+     * The value must include offset.
+     */
+    void getStartAndEndTimes(LongArray times, long offset) {
+        long startTime = offset + getStartDelay();
+        if (times.indexOf(startTime) < 0) {
+            times.add(startTime);
+        }
+        long duration = getTotalDuration();
+        if (duration != DURATION_INFINITE) {
+            long endTime = duration + offset;
+            if (times.indexOf(endTime) < 0) {
+                times.add(endTime);
+            }
+        }
+    }
 
     /**
      * <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index bc8db02..257adfe 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,9 +23,11 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.LongArray;
 import android.view.animation.Animation;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -181,6 +183,16 @@
      */
     private long mPauseTime = -1;
 
+    /**
+     * The start and stop times of all descendant animators.
+     */
+    private long[] mChildStartAndStopTimes;
+
+    /**
+     * Tracks whether we've notified listeners of the onAnimationStart() event.
+     */
+    private boolean mStartListenersCalled;
+
     // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
     private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -729,14 +741,7 @@
             startAnimation();
         }
 
-        if (mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, inReverse);
-            }
-        }
+        notifyStartListeners(inReverse);
         if (isEmptySet) {
             // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
             // onAnimationEnd() right away.
@@ -744,6 +749,32 @@
         }
     }
 
+    private void notifyStartListeners(boolean inReverse) {
+        if (mListeners != null && !mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                AnimatorListener listener = tmpListeners.get(i);
+                listener.onAnimationStart(this, inReverse);
+            }
+        }
+        mStartListenersCalled = true;
+    }
+
+    private void notifyEndListeners(boolean inReverse) {
+        if (mListeners != null && mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                AnimatorListener listener = tmpListeners.get(i);
+                listener.onAnimationEnd(this, inReverse);
+            }
+        }
+        mStartListenersCalled = false;
+    }
+
     // Returns true if set is empty or contains nothing but animator sets with no start delay.
     private static boolean isEmptySet(AnimatorSet set) {
         if (set.getStartDelay() > 0) {
@@ -779,26 +810,25 @@
 
     @Override
     void skipToEndValue(boolean inReverse) {
-        if (!isInitialized()) {
-            throw new UnsupportedOperationException("Children must be initialized.");
-        }
-
         // This makes sure the animation events are sorted an up to date.
         initAnimation();
+        initChildren();
 
         // Calling skip to the end in the sequence that they would be called in a forward/reverse
         // run, such that the sequential animations modifying the same property would have
         // the right value in the end.
         if (inReverse) {
             for (int i = mEvents.size() - 1; i >= 0; i--) {
-                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+                AnimationEvent event = mEvents.get(i);
+                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                    event.mNode.mAnimation.skipToEndValue(true);
                 }
             }
         } else {
             for (int i = 0; i < mEvents.size(); i++) {
-                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
-                    mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+                AnimationEvent event = mEvents.get(i);
+                if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                    event.mNode.mAnimation.skipToEndValue(false);
                 }
             }
         }
@@ -814,72 +844,216 @@
      * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
      * as needed, based on the last play time and current play time.
      */
-    @Override
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
-        if (currentPlayTime < 0 || lastPlayTime < 0) {
+    private void animateBasedOnPlayTime(
+            long currentPlayTime,
+            long lastPlayTime,
+            boolean inReverse,
+            boolean notify
+    ) {
+        if (currentPlayTime < 0 || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
         // TODO: take into account repeat counts and repeat callback when repeat is implemented.
-        // Clamp currentPlayTime and lastPlayTime
 
-        // TODO: Make this more efficient
-
-        // Convert the play times to the forward direction.
         if (inReverse) {
-            if (getTotalDuration() == DURATION_INFINITE) {
-                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
-                        + " duration");
+            long duration = getTotalDuration();
+            if (duration == DURATION_INFINITE) {
+                throw new UnsupportedOperationException(
+                        "Cannot reverse AnimatorSet with infinite duration"
+                );
             }
-            long duration = getTotalDuration() - mStartDelay;
+            // Convert the play times to the forward direction.
             currentPlayTime = Math.min(currentPlayTime, duration);
             currentPlayTime = duration - currentPlayTime;
             lastPlayTime = duration - lastPlayTime;
-            inReverse = false;
         }
 
-        ArrayList<Node> unfinishedNodes = new ArrayList<>();
-        // Assumes forward playing from here on.
-        for (int i = 0; i < mEvents.size(); i++) {
-            AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
-                break;
-            }
+        long[] startEndTimes = ensureChildStartAndEndTimes();
+        int index = findNextIndex(lastPlayTime, startEndTimes);
+        int endIndex = findNextIndex(currentPlayTime, startEndTimes);
 
-            // This animation started prior to the current play time, and won't finish before the
-            // play time, add to the unfinished list.
-            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                if (event.mNode.mEndTime == DURATION_INFINITE
-                        || event.mNode.mEndTime > currentPlayTime) {
-                    unfinishedNodes.add(event.mNode);
+        // Change values at the start/end times so that values are set in the right order.
+        // We don't want an animator that would finish before another to override the value
+        // set by another animator that finishes earlier.
+        if (currentPlayTime >= lastPlayTime) {
+            while (index < endIndex) {
+                long playTime = startEndTimes[index];
+                if (lastPlayTime != playTime) {
+                    animateSkipToEnds(playTime, lastPlayTime, notify);
+                    animateValuesInRange(playTime, lastPlayTime, notify);
+                    lastPlayTime = playTime;
+                }
+                index++;
+            }
+        } else {
+            while (index > endIndex) {
+                index--;
+                long playTime = startEndTimes[index];
+                if (lastPlayTime != playTime) {
+                    animateSkipToEnds(playTime, lastPlayTime, notify);
+                    animateValuesInRange(playTime, lastPlayTime, notify);
+                    lastPlayTime = playTime;
                 }
             }
-            // For animations that do finish before the play time, end them in the sequence that
-            // they would in a normal run.
-            if (event.mEvent == AnimationEvent.ANIMATION_END) {
-                // Skip to the end of the animation.
-                event.mNode.mAnimation.skipToEndValue(false);
+        }
+        if (currentPlayTime != lastPlayTime) {
+            animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
+            animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+        }
+    }
+
+    /**
+     * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+     * is returned. Otherwise, it returns the index at which it would be placed if it were
+     * to be inserted.
+     */
+    private int findNextIndex(long playTime, long[] startEndTimes) {
+        int index = Arrays.binarySearch(startEndTimes, playTime);
+        if (index < 0) {
+            index = -index - 1;
+        } else {
+            index++;
+        }
+        return index;
+    }
+
+    @Override
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+        initAnimation();
+
+        if (lastPlayTime > currentPlayTime) {
+            if (notify) {
+                notifyStartListeners(true);
+            }
+            for (int i = mEvents.size() - 1; i >= 0; i--) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_END
+                        && node.mStartTime != DURATION_INFINITE
+                ) {
+                    Animator animator = node.mAnimation;
+                    long start = node.mStartTime;
+                    long end = node.mTotalDuration == DURATION_INFINITE
+                            ? Long.MAX_VALUE : node.mEndTime;
+                    if (currentPlayTime <= start && start < lastPlayTime) {
+                        animator.animateSkipToEnds(
+                                0,
+                                lastPlayTime - node.mStartTime,
+                                notify
+                        );
+                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
+                        animator.animateSkipToEnds(
+                                currentPlayTime - node.mStartTime,
+                                lastPlayTime - node.mStartTime,
+                                notify
+                        );
+                    }
+                }
+            }
+            if (currentPlayTime <= 0 && notify) {
+                notifyEndListeners(true);
+            }
+        } else {
+            if (notify) {
+                notifyStartListeners(false);
+            }
+            int eventsSize = mEvents.size();
+            for (int i = 0; i < eventsSize; i++) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                        && node.mStartTime != DURATION_INFINITE
+                ) {
+                    Animator animator = node.mAnimation;
+                    long start = node.mStartTime;
+                    long end = node.mTotalDuration == DURATION_INFINITE
+                            ? Long.MAX_VALUE : node.mEndTime;
+                    if (lastPlayTime < end && end <= currentPlayTime) {
+                        animator.animateSkipToEnds(
+                                end - node.mStartTime,
+                                lastPlayTime - node.mStartTime,
+                                notify
+                        );
+                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
+                        animator.animateSkipToEnds(
+                                currentPlayTime - node.mStartTime,
+                                lastPlayTime - node.mStartTime,
+                                notify
+                        );
+                    }
+                }
+            }
+            if (currentPlayTime >= getTotalDuration() && notify) {
+                notifyEndListeners(false);
+            }
+        }
+    }
+
+    @Override
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+        initAnimation();
+
+        if (notify) {
+            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+                notifyStartListeners(false);
+            } else {
+                long duration = getTotalDuration();
+                if (duration >= 0
+                        && (lastPlayTime > duration || (lastPlayTime == duration
+                        && currentPlayTime < duration))
+                ) {
+                    notifyStartListeners(true);
+                }
             }
         }
 
-        // Seek unfinished animation to the right time.
-        for (int i = 0; i < unfinishedNodes.size(); i++) {
-            Node node = unfinishedNodes.get(i);
-            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
-            if (!inReverse) {
-                playTime -= node.mAnimation.getStartDelay();
-            }
-            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
-        }
-
-        // Seek not yet started animations.
-        for (int i = 0; i < mEvents.size(); i++) {
+        int eventsSize = mEvents.size();
+        for (int i = 0; i < eventsSize; i++) {
             AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime
-                    && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                event.mNode.mAnimation.skipToEndValue(true);
+            Node node = event.mNode;
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                    && node.mStartTime != DURATION_INFINITE
+            ) {
+                Animator animator = node.mAnimation;
+                long start = node.mStartTime;
+                long end = node.mTotalDuration == DURATION_INFINITE
+                        ? Long.MAX_VALUE : node.mEndTime;
+                if ((start < currentPlayTime && currentPlayTime < end)
+                        || (start == currentPlayTime && lastPlayTime < start)
+                        || (end == currentPlayTime && lastPlayTime > end)
+                ) {
+                    animator.animateValuesInRange(
+                            currentPlayTime - node.mStartTime,
+                            Math.max(-1, lastPlayTime - node.mStartTime),
+                            notify
+                    );
+                }
             }
         }
+    }
 
+    private long[] ensureChildStartAndEndTimes() {
+        if (mChildStartAndStopTimes == null) {
+            LongArray startAndEndTimes = new LongArray();
+            getStartAndEndTimes(startAndEndTimes, 0);
+            long[] times = startAndEndTimes.toArray();
+            Arrays.sort(times);
+            mChildStartAndStopTimes = times;
+        }
+        return mChildStartAndStopTimes;
+    }
+
+    @Override
+    void getStartAndEndTimes(LongArray times, long offset) {
+        int eventsSize = mEvents.size();
+        for (int i = 0; i < eventsSize; i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                    && event.mNode.mStartTime != DURATION_INFINITE
+            ) {
+                event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+            }
+        }
     }
 
     @Override
@@ -899,10 +1073,6 @@
         return mChildrenInitialized;
     }
 
-    private void skipToStartValue(boolean inReverse) {
-        skipToEndValue(!inReverse);
-    }
-
     /**
      * Sets the position of the animation to the specified point in time. This time should
      * be between 0 and the total duration of the animation, including any repetition. If
@@ -910,6 +1080,11 @@
      * set to this time; it will simply set the time to this value and perform any appropriate
      * actions based on that time. If the animation is already running, then setCurrentPlayTime()
      * will set the current playing time to this value and continue playing from that point.
+     * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+     * that hasn't been {@link #start()}ed, will issue
+     * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+     * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+     * events.
      *
      * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
      *                 Unless the animation is reversing, the playtime is considered the time since
@@ -926,29 +1101,27 @@
         if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
                 || playTime < 0) {
             throw new UnsupportedOperationException("Error: Play time should always be in between"
-                    + "0 and duration.");
+                    + " 0 and duration.");
         }
 
         initAnimation();
 
+        long lastPlayTime = mSeekState.getPlayTime();
         if (!isStarted() || isPaused()) {
-            if (mReversing) {
+            if (mReversing && !isStarted()) {
                 throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
                         + " should not be set when AnimatorSet is not started.");
             }
             if (!mSeekState.isActive()) {
                 findLatestEventIdForTime(0);
-                // Set all the values to start values.
                 initChildren();
+                // Set all the values to start values.
+                skipToEndValue(!mReversing);
                 mSeekState.setPlayTime(0, mReversing);
             }
-            animateBasedOnPlayTime(playTime, 0, mReversing);
-            mSeekState.setPlayTime(playTime, mReversing);
-        } else {
-            // If the animation is running, just set the seek time and wait until the next frame
-            // (i.e. doAnimationFrame(...)) to advance the animation.
-            mSeekState.setPlayTime(playTime, mReversing);
         }
+        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+        mSeekState.setPlayTime(playTime, mReversing);
     }
 
     /**
@@ -981,10 +1154,16 @@
     private void initChildren() {
         if (!isInitialized()) {
             mChildrenInitialized = true;
-            // Forcefully initialize all children based on their end time, so that if the start
-            // value of a child is dependent on a previous animation, the animation will be
-            // initialized after the the previous animations have been advanced to the end.
-            skipToEndValue(false);
+
+            // We have to initialize all the start values so that they are based on the previous
+            // values.
+            long[] times = ensureChildStartAndEndTimes();
+
+            long previousTime = -1;
+            for (long time : times) {
+                animateBasedOnPlayTime(time, previousTime, false, false);
+                previousTime = time;
+            }
         }
     }
 
@@ -1058,7 +1237,7 @@
         for (int i = 0; i < mPlayingSet.size(); i++) {
             Node node = mPlayingSet.get(i);
             if (!node.mEnded) {
-                pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
+                pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
             }
         }
 
@@ -1129,7 +1308,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
                     // end event:
-                    pulseFrame(node, getPlayTimeForNode(playTime, node));
+                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                 }
             }
         } else {
@@ -1150,7 +1329,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
                     // start event:
-                    pulseFrame(node, getPlayTimeForNode(playTime, node));
+                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                 }
             }
         }
@@ -1172,11 +1351,15 @@
         }
     }
 
-    private long getPlayTimeForNode(long overallPlayTime, Node node) {
-        return getPlayTimeForNode(overallPlayTime, node, mReversing);
+    private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+        return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
     }
 
-    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+    private long getPlayTimeForNodeIncludingDelay(
+            long overallPlayTime,
+            Node node,
+            boolean inReverse
+    ) {
         if (inReverse) {
             overallPlayTime = getTotalDuration() - overallPlayTime;
             return node.mEndTime - overallPlayTime;
@@ -1198,26 +1381,8 @@
         }
         // Set the child animators to the right end:
         if (mShouldResetValuesAtStart) {
-            if (isInitialized()) {
-                skipToEndValue(!mReversing);
-            } else if (mReversing) {
-                // Reversing but haven't initialized all the children yet.
-                initChildren();
-                skipToEndValue(!mReversing);
-            } else {
-                // If not all children are initialized and play direction is forward
-                for (int i = mEvents.size() - 1; i >= 0; i--) {
-                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                        Animator anim = mEvents.get(i).mNode.mAnimation;
-                        // Only reset the animations that have been initialized to start value,
-                        // so that if they are defined without a start value, they will get the
-                        // values set at the right time (i.e. the next animation run)
-                        if (anim.isInitialized()) {
-                            anim.skipToEndValue(true);
-                        }
-                    }
-                }
-            }
+            initChildren();
+            skipToEndValue(!mReversing);
         }
 
         if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1292,15 +1457,7 @@
 
         // No longer receive callbacks
         removeAnimationCallback();
-        // Call end listener
-        if (mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, mReversing);
-            }
-        }
+        notifyEndListeners(mReversing);
         removeAnimationEndListener();
         mSelfPulse = true;
         mReversing = false;
@@ -1922,11 +2079,11 @@
         }
 
         void setPlayTime(long playTime, boolean inReverse) {
-            // TODO: This can be simplified.
-
             // Clamp the play time
             if (getTotalDuration() != DURATION_INFINITE) {
                 mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+            } else {
+                mPlayTime = playTime;
             }
             mPlayTime = Math.max(0, mPlayTime);
             mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6ab7ae6..7009725 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,8 +324,9 @@
             listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
         }
 
-        for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
-            final DurationScaleChangeListener listener = listenerRef.get();
+        int listenersSize = listenerCopy.size();
+        for (int i = 0; i < listenersSize; i++) {
+            final DurationScaleChangeListener listener = listenerCopy.get(i).get();
             if (listener != null) {
                 listener.onChanged(durationScale);
             }
@@ -624,7 +625,7 @@
     public void setValues(PropertyValuesHolder... values) {
         int numValues = values.length;
         mValues = values;
-        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+        mValuesMap = new HashMap<>(numValues);
         for (int i = 0; i < numValues; ++i) {
             PropertyValuesHolder valuesHolder = values[i];
             mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -658,9 +659,11 @@
     @CallSuper
     void initAnimation() {
         if (!mInitialized) {
-            int numValues = mValues.length;
-            for (int i = 0; i < numValues; ++i) {
-                mValues[i].init();
+            if (mValues != null) {
+                int numValues = mValues.length;
+                for (int i = 0; i < numValues; ++i) {
+                    mValues[i].init();
+                }
             }
             mInitialized = true;
         }
@@ -1105,18 +1108,30 @@
         }
     }
 
-    private void notifyStartListeners() {
+    private void notifyStartListeners(boolean isReversing) {
         if (mListeners != null && !mStartListenersCalled) {
             ArrayList<AnimatorListener> tmpListeners =
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, mReversing);
+                tmpListeners.get(i).onAnimationStart(this, isReversing);
             }
         }
         mStartListenersCalled = true;
     }
 
+    private void notifyEndListeners(boolean isReversing) {
+        if (mListeners != null && mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, isReversing);
+            }
+        }
+        mStartListenersCalled = false;
+    }
+
     /**
      * Start the animation playing. This version of start() takes a boolean flag that indicates
      * whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1207,12 +1222,16 @@
         if ((mStarted || mRunning) && mListeners != null) {
             if (!mRunning) {
                 // If it's not yet running, then start listeners weren't called. Call them now.
-                notifyStartListeners();
+                notifyStartListeners(mReversing);
             }
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            for (AnimatorListener listener : tmpListeners) {
-                listener.onAnimationCancel(this);
+            int listenersSize = mListeners.size();
+            if (listenersSize > 0) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                for (int i = 0; i < listenersSize; i++) {
+                    AnimatorListener listener = tmpListeners.get(i);
+                    listener.onAnimationCancel(this);
+                }
             }
         }
         endAnimation();
@@ -1317,22 +1336,14 @@
         boolean notify = (mStarted || mRunning) && mListeners != null;
         if (notify && !mRunning) {
             // If it's not yet running, then start listeners weren't called. Call them now.
-            notifyStartListeners();
+            notifyStartListeners(mReversing);
         }
         mRunning = false;
         mStarted = false;
-        mStartListenersCalled = false;
         mLastFrameTime = -1;
         mFirstFrameTime = -1;
         mStartTime = -1;
-        if (notify && mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, mReversing);
-            }
-        }
+        notifyEndListeners(mReversing);
         // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
         mReversing = false;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1359,9 +1370,8 @@
         } else {
             mOverallFraction = 0f;
         }
-        if (mListeners != null) {
-            notifyStartListeners();
-        }
+
+        notifyStartListeners(mReversing);
     }
 
     /**
@@ -1452,16 +1462,32 @@
      * will be called.
      */
     @Override
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
-        if (currentPlayTime < 0 || lastPlayTime < 0) {
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+        if (currentPlayTime < 0 || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
 
         initAnimation();
+        long duration = getTotalDuration();
+        if (notify) {
+            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+                notifyStartListeners(false);
+            } else if (lastPlayTime > duration
+                    || (lastPlayTime == duration && currentPlayTime < duration)
+            ) {
+                notifyStartListeners(true);
+            }
+        }
+        if (duration >= 0) {
+            lastPlayTime = Math.min(duration, lastPlayTime);
+        }
+        lastPlayTime -= mStartDelay;
+        currentPlayTime -= mStartDelay;
+
         // Check whether repeat callback is needed only when repeat count is non-zero
         if (mRepeatCount > 0) {
-            int iteration = (int) (currentPlayTime / mDuration);
-            int lastIteration = (int) (lastPlayTime / mDuration);
+            int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+            int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
 
             // Clamp iteration to [0, mRepeatCount]
             iteration = Math.min(iteration, mRepeatCount);
@@ -1477,16 +1503,37 @@
             }
         }
 
-        if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
-            skipToEndValue(inReverse);
+        if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
+            throw new IllegalStateException("Can't animate a value outside of the duration");
         } else {
             // Find the current fraction:
-            float fraction = currentPlayTime / (float) mDuration;
-            fraction = getCurrentIterationFraction(fraction, inReverse);
+            float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
+            fraction = getCurrentIterationFraction(fraction, false);
             animateValue(fraction);
         }
     }
 
+    @Override
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+        boolean inReverse = currentPlayTime < lastPlayTime;
+        boolean doSkip;
+        if (currentPlayTime <= 0 && lastPlayTime > 0) {
+            doSkip = true;
+        } else {
+            long duration = getTotalDuration();
+            doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+        }
+        if (doSkip) {
+            if (notify) {
+                notifyStartListeners(inReverse);
+            }
+            skipToEndValue(inReverse);
+            if (notify) {
+                notifyEndListeners(inReverse);
+            }
+        }
+    }
+
     /**
      * Internal use only.
      * Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1641,6 +1688,9 @@
             Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
                     (int) (fraction * 1000));
         }
+        if (mValues == null) {
+            return;
+        }
         fraction = mInterpolator.getInterpolation(fraction);
         mCurrentFraction = fraction;
         int numValues = mValues.length;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a14f3d3..84320ca 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1439,9 +1439,18 @@
     public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
             AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
 
+    /**
+     * Allows an application to capture bugreport directly without consent dialog when using the
+     * bugreporting API on userdebug/eng build.
+     *
+     * @hide
+     */
+    public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
+            AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 131;
+    public static final int _NUM_OP = 132;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -2011,6 +2020,16 @@
     public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
             "android:system_exempt_from_fgs_stop_button";
 
+    /**
+     * Allows an application to capture bugreport directly without consent dialog when using the
+     * bugreporting API on userdebug/eng build.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
+            "android:capture_consentless_bugreport_on_userdebug_build";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2108,6 +2127,7 @@
             OP_RUN_LONG_JOBS,
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
+            OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2515,7 +2535,13 @@
                 .build(),
         new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
                 OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
-                "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
+                "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build(),
+        new AppOpInfo.Builder(
+                OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+                OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+                "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
+                .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
+                .build()
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9ecf8ff..88765c3 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -909,7 +909,6 @@
     /**
      * Clear the {@link BundleMerger} object that was previously set using
      * {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
-     *
      * @hide
      */
     public void clearDeliveryGroupExtrasMerger() {
@@ -917,6 +916,36 @@
     }
 
     /**
+     * Set PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     *
+     * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+     * of states
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+        super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+    }
+
+    /**
+     * Get PendingIntent activity is allowed to be started in the background if the caller can start
+     * background activities.
+     *
+     * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+     * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+     * not match the actual behavior if the value was not explicitly set.
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        return super.isPendingIntentBackgroundActivityLaunchAllowed();
+    }
+
+
+    /**
      * Sets the mode for allowing or denying the senders privileges to start background activities
      * to the PendingIntent.
      *
@@ -924,7 +953,6 @@
      * methods. A privileged sender of a PendingIntent should only grant
      * MODE_BACKGROUND_ACTIVITY_START_ALLOWED if the PendingIntent is from a trusted source and/or
      * executed on behalf the user.
-     *
      * @hide
      */
     @SystemApi
@@ -936,6 +964,19 @@
     }
 
     /**
+     * Gets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * @see #setPendingIntentBackgroundActivityStartMode(int)
+     * @hide
+     */
+    @SystemApi
+    @Override // to narrow down the return type
+    public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+        return super.getPendingIntentBackgroundActivityStartMode();
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index fae6887..4a5836c 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -178,6 +178,11 @@
     }
 
     @Override
+    public ParcelFileDescriptor getWallpaperFile(int which, boolean getCropped) {
+        return unsupported();
+    }
+
+    @Override
     public void forgetLoadedWallpaper() {
         unsupported();
     }
@@ -188,6 +193,11 @@
     }
 
     @Override
+    public ParcelFileDescriptor getWallpaperInfoFile() {
+        return unsupported();
+    }
+
+    @Override
     public WallpaperInfo getWallpaperInfoForUser(int userId) {
         return unsupported();
     }
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index b1ed152..f4373a6 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -74,7 +74,8 @@
      * Get the wallpaper for a given user.
      */
     ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
-            IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId);
+            IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId,
+            boolean getCropped);
 
     /**
      * Retrieve the given user's current wallpaper ID of the given kind.
@@ -96,6 +97,12 @@
     WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
 
     /**
+     * Return a file descriptor for the file that contains metadata about the given user's
+     * wallpaper.
+     */
+    ParcelFileDescriptor getWallpaperInfoFile(int userId);
+
+    /**
      * Clear the system wallpaper.
      */
     void clearWallpaper(in String callingPackage, int which, int userId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c081d82..9b348fc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7947,8 +7947,6 @@
          * @hide
          */
         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
-            // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
-            // see reduceImageSizes.
             mShortcutIcon = conversationIcon;
             return this;
         }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 1187459..f3a83d8 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -640,7 +640,7 @@
                 Bundle params = new Bundle();
                 try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
                         context.getOpPackageName(), context.getAttributionTag(), this, which,
-                        params, userId)) {
+                        params, userId, /* getCropped = */ true)) {
                     // Let's peek user wallpaper first.
                     if (pfd != null) {
                         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -690,7 +690,7 @@
                 Bundle params = new Bundle();
                 ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
                         context.getOpPackageName(), context.getAttributionTag(), this, which,
-                        params, userId);
+                        params, userId, /* getCropped = */ true);
 
                 if (pfd != null) {
                     try (BufferedInputStream bis = new BufferedInputStream(
@@ -1437,6 +1437,27 @@
      */
     @UnsupportedAppUsage
     public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) {
+        return getWallpaperFile(which, userId, /* getCropped = */ true);
+    }
+
+    /**
+     * Version of {@link #getWallpaperFile(int)} that allows specifying whether to get the
+     * cropped version of the wallpaper file or the original.
+     *
+     * @param which The wallpaper whose image file is to be retrieved.  Must be a single
+     *    defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @param getCropped If true the cropped file will be retrieved, if false the original will
+     *                   be retrieved.
+     *
+     * @hide
+     */
+    @Nullable
+    public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) {
+        return getWallpaperFile(which, mContext.getUserId(), getCropped);
+    }
+
+    private ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId,
+            boolean getCropped) {
         checkExactlyOneWallpaperFlagSet(which);
 
         if (sGlobals.mService == null) {
@@ -1446,7 +1467,8 @@
             try {
                 Bundle outParams = new Bundle();
                 return sGlobals.mService.getWallpaperWithFeature(mContext.getOpPackageName(),
-                        mContext.getAttributionTag(), null, which, outParams, userId);
+                        mContext.getAttributionTag(), null, which, outParams,
+                        userId, getCropped);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (SecurityException e) {
@@ -1530,6 +1552,28 @@
     }
 
     /**
+     * Get an open, readable file descriptor for the file that contains metadata about the
+     * context user's wallpaper.
+     *
+     * The caller is responsible for closing the file descriptor when done ingesting the file.
+     *
+     * @hide
+     */
+    @Nullable
+    public ParcelFileDescriptor getWallpaperInfoFile() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        } else {
+            try {
+                return sGlobals.mService.getWallpaperInfoFile(mContext.getUserId());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Get the ID of the current wallpaper of the given kind.  If there is no
      * such wallpaper configured, returns a negative number.
      *
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index e96a2c1..4f49b8d 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -18,6 +18,7 @@
 
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceParams;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -41,10 +42,12 @@
      * @param params The parameters for creating this virtual device. See {@link
      *   VirtualDeviceManager.VirtualDeviceParams}.
      * @param activityListener The listener to listen for activity changes in a virtual device.
+     * @param soundEffectListener The listener to listen for sound effect playback requests.
      */
     IVirtualDevice createVirtualDevice(
             in IBinder token, String packageName, int associationId,
-            in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener);
+            in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener,
+            in IVirtualDeviceSoundEffectListener soundEffectListener);
 
     /**
      * Returns the details of all available virtual devices.
@@ -92,4 +95,13 @@
      * if there's none.
      */
     int getAudioRecordingSessionId(int deviceId);
+
+    /**
+     * Triggers sound effect playback on virtual device.
+     *
+     * @param deviceId id of the virtual device.
+     * @param sound effect type corresponding to
+     *     {@code android.media.AudioManager.SystemSoundEffect}
+     */
+    void playSoundEffect(int deviceId, int effectType);
 }
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
new file mode 100644
index 0000000..91c209f
--- /dev/null
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+/**
+ * Interface to listen for sound effect playback on Virtual Device.
+ *
+ * @hide
+ */
+oneway interface IVirtualDeviceSoundEffectListener {
+
+
+    /**
+     * Called when there's sound effect to be played on Virtual Device.
+     *
+     * @param sound effect type corresponding to
+     *     {@code android.media.AudioManager.SystemSoundEffect}
+     */
+    void onPlaySoundEffect(int effectType);
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 3bc1628..1bc3091 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -69,6 +69,8 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -333,10 +335,15 @@
      * @hide
      */
     public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
-        //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
-        // with device session id.
-        // For now, this is intentionally left empty and effectively disables sound effects for
-        // virtual devices with custom device audio policy.
+        if (mService == null) {
+            Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service.");
+            return;
+        }
+        try {
+            mService.playSoundEffect(deviceId, effectType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -359,6 +366,10 @@
         private final ArrayMap<IntentInterceptorCallback,
                      VirtualIntentInterceptorDelegate> mIntentInterceptorListeners =
                 new ArrayMap<>();
+        private final Object mSoundEffectListenersLock = new Object();
+        @GuardedBy("mSoundEffectListenersLock")
+        private final ArrayMap<SoundEffectListener, SoundEffectListenerDelegate>
+                mSoundEffectListeners = new ArrayMap<>();
         private final IVirtualDeviceActivityListener mActivityListenerBinder =
                 new IVirtualDeviceActivityListener.Stub() {
 
@@ -387,6 +398,22 @@
                         }
                     }
                 };
+        private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
+                new IVirtualDeviceSoundEffectListener.Stub() {
+                    @Override
+                    public void onPlaySoundEffect(int soundEffect) {
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            synchronized (mSoundEffectListenersLock) {
+                                for (int i = 0; i < mSoundEffectListeners.size(); i++) {
+                                    mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
+                                }
+                            }
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
+                };
         @Nullable
         private VirtualCameraDevice mVirtualCameraDevice;
         @NonNull
@@ -407,7 +434,8 @@
                     mContext.getPackageName(),
                     associationId,
                     params,
-                    mActivityListenerBinder);
+                    mActivityListenerBinder,
+                    mSoundEffectListener);
             final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
             for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
                 mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
@@ -947,6 +975,35 @@
         }
 
         /**
+         * Adds a sound effect listener.
+         *
+         * @param executor The executor where the listener is executed on.
+         * @param soundEffectListener The listener to add.
+         * @see #removeActivityListener(ActivityListener)
+         */
+        public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
+                @NonNull SoundEffectListener soundEffectListener) {
+            final SoundEffectListenerDelegate delegate =
+                    new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
+                            Objects.requireNonNull(soundEffectListener));
+            synchronized (mSoundEffectListenersLock) {
+                mSoundEffectListeners.put(soundEffectListener, delegate);
+            }
+        }
+
+        /**
+         * Removes a sound effect listener previously added with {@link #addActivityListener}.
+         *
+         * @param soundEffectListener The listener to remove.
+         * @see #addActivityListener(Executor, ActivityListener)
+         */
+        public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
+            synchronized (mSoundEffectListenersLock) {
+                mSoundEffectListeners.remove(soundEffectListener);
+            }
+        }
+
+        /**
          * Registers an intent interceptor that will intercept an intent attempting to launch
          * when matching the provided IntentFilter and calls the callback with the intercepted
          * intent.
@@ -1090,4 +1147,38 @@
             }
         }
     }
+
+    /**
+     * Listener for system sound effect playback on virtual device.
+     * @hide
+     */
+    @SystemApi
+    public interface SoundEffectListener {
+
+        /**
+         * Called when there's a system sound effect to be played on virtual device.
+         *
+         * @param effectType - system sound effect type, see
+         *     {@code android.media.AudioManager.SystemSoundEffect}
+         */
+        void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
+    }
+
+    /**
+     * A wrapper for {@link SoundEffectListener} that executes callbacks on the given executor.
+     */
+    private static class SoundEffectListenerDelegate {
+        @NonNull private final SoundEffectListener mSoundEffectListener;
+        @NonNull private final Executor mExecutor;
+
+        private SoundEffectListenerDelegate(Executor executor,
+                SoundEffectListener soundEffectCallback) {
+            mSoundEffectListener = soundEffectCallback;
+            mExecutor = executor;
+        }
+
+        public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
+            mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
+        }
+    }
 }
diff --git a/core/java/android/content/pm/ApkChecksum.java b/core/java/android/content/pm/ApkChecksum.java
index d550f41..88a1960 100644
--- a/core/java/android/content/pm/ApkChecksum.java
+++ b/core/java/android/content/pm/ApkChecksum.java
@@ -36,7 +36,7 @@
  *
  * @see PackageManager#requestChecksums
  */
-@DataClass(genHiddenConstructor = true)
+@DataClass(genHiddenConstructor = true, genToString = true)
 @DataClass.Suppress({"getChecksum"})
 public final class ApkChecksum implements Parcelable {
     /**
@@ -178,6 +178,20 @@
 
     @Override
     @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ApkChecksum { " +
+                "splitName = " + mSplitName + ", " +
+                "checksum = " + mChecksum + ", " +
+                "installerPackageName = " + mInstallerPackageName + ", " +
+                "installerCertificate = " + java.util.Arrays.toString(mInstallerCertificate) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
@@ -235,10 +249,10 @@
     };
 
     @DataClass.Generated(
-            time = 1619810171079L,
+            time = 1674080488372L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
-            inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 44747fa..e38cb65 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -406,24 +406,6 @@
     }
 
     /**
-     * Returns true if the user is a split system user.
-     * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
-     * the method always returns false.
-     */
-    public boolean isSystemOnly() {
-        return isSystemOnly(id);
-    }
-
-    /**
-     * Returns true if the given user is a split system user.
-     * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
-     * the method always returns false.
-     */
-    public static boolean isSystemOnly(int userId) {
-        return userId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser();
-    }
-
-    /**
      * @return true if this user can be switched to.
      **/
     public boolean supportsSwitchTo() {
@@ -454,7 +436,7 @@
         if (isProfile() || isGuest() || isRestricted()) {
             return false;
         }
-        if (UserManager.isSplitSystemUser() || UserManager.isHeadlessSystemUserMode()) {
+        if (UserManager.isHeadlessSystemUserMode()) {
             return id != UserHandle.USER_SYSTEM;
         } else {
             return id == UserHandle.USER_SYSTEM;
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index fefa60a..84cc9a8 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -28,7 +28,7 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#executeCreateCredential(CreateCredentialRequest,
+ * {@link CredentialManager#createCredential(CreateCredentialRequest,
  * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class CreateCredentialException extends Exception {
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no credential is available for the given {@link
-     * CredentialManager#executeCreateCredential(CreateCredentialRequest, Activity,
+     * CredentialManager#createCredential(CreateCredentialRequest, Activity,
      * CancellationSignal, Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index be887a5..26f8831 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -54,7 +54,7 @@
     /**
      * Determines whether the request must only be fulfilled by a system provider.
      */
-    private final boolean mRequireSystemProvider;
+    private final boolean mIsSystemProviderRequired;
 
     /**
      * Returns the requested credential type.
@@ -99,8 +99,8 @@
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
      */
-    public boolean requireSystemProvider() {
-        return mRequireSystemProvider;
+    public boolean isSystemProviderRequired() {
+        return mIsSystemProviderRequired;
     }
 
     @Override
@@ -108,7 +108,7 @@
         dest.writeString8(mType);
         dest.writeBundle(mCredentialData);
         dest.writeBundle(mCandidateQueryData);
-        dest.writeBoolean(mRequireSystemProvider);
+        dest.writeBoolean(mIsSystemProviderRequired);
     }
 
     @Override
@@ -122,7 +122,7 @@
                 + "type=" + mType
                 + ", credentialData=" + mCredentialData
                 + ", candidateQueryData=" + mCandidateQueryData
-                + ", requireSystemProvider=" + mRequireSystemProvider
+                + ", isSystemProviderRequired=" + mIsSystemProviderRequired
                 + "}";
     }
 
@@ -133,7 +133,8 @@
      * @param credentialData the full credential creation request data
      * @param candidateQueryData the partial request data that will be sent to the provider
      *                           during the initial creation candidate query stage
-     * @param requireSystemProvider whether the request must only be fulfilled by a system provider
+     * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+     *                                provider
      *
      * @throws IllegalArgumentException If type is empty.
      */
@@ -141,19 +142,19 @@
             @NonNull String type,
             @NonNull Bundle credentialData,
             @NonNull Bundle candidateQueryData,
-            boolean requireSystemProvider) {
+            boolean isSystemProviderRequired) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
         mCandidateQueryData = requireNonNull(candidateQueryData,
                 "candidateQueryData must not be null");
-        mRequireSystemProvider = requireSystemProvider;
+        mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
     private CreateCredentialRequest(@NonNull Parcel in) {
         String type = in.readString8();
         Bundle credentialData = in.readBundle();
         Bundle candidateQueryData = in.readBundle();
-        boolean requireSystemProvider = in.readBoolean();
+        boolean isSystemProviderRequired = in.readBoolean();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
@@ -161,7 +162,7 @@
         AnnotationValidations.validate(NonNull.class, null, mCredentialData);
         mCandidateQueryData = candidateQueryData;
         AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
-        mRequireSystemProvider = requireSystemProvider;
+        mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
     public static final @NonNull Parcelable.Creator<CreateCredentialRequest> CREATOR =
diff --git a/core/java/android/credentials/CredentialDescription.aidl b/core/java/android/credentials/CredentialDescription.aidl
new file mode 100644
index 0000000..1b5739e
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+/**
+ * @hide
+ */
+parcelable CredentialDescription;
\ No newline at end of file
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
new file mode 100644
index 0000000..b4310f2
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.credentials.CredentialEntry;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents the type and contained data fields of a {@link Credential}.
+ * @hide
+ */
+public final class CredentialDescription implements Parcelable {
+
+    /**
+     * The credential type.
+     */
+    @NonNull
+    private final String mType;
+
+    /**
+     * The flattened JSON string that will be matched with requests.
+     */
+    @NonNull
+    private final String mFlattenedRequestString;
+
+    /**
+     * The entry to be used in the UI.
+     */
+    @NonNull
+    private final List<CredentialEntry> mCredentialEntries;
+
+    /**
+     * Constructs a {@link CredentialDescription}.
+     *
+     * @param type the type of the credential returned.
+     * @param flattenedRequestString flattened JSON string that will be matched with requests.
+     * @param credentialEntries a list of {@link  CredentialEntry}s that have been returned
+     *                          to the developer upon credential creation.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public CredentialDescription(@NonNull String type,
+            @NonNull String flattenedRequestString,
+            @NonNull List<CredentialEntry> credentialEntries) {
+        mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+        mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
+        mCredentialEntries = Objects.requireNonNull(credentialEntries);
+    }
+
+    private CredentialDescription(@NonNull Parcel in) {
+        String type = in.readString8();
+        String flattenedRequestString = in.readString();
+        List<CredentialEntry> entries = new ArrayList<>();
+        in.readTypedList(entries, CredentialEntry.CREATOR);
+
+        mType = type;
+        AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
+        mFlattenedRequestString = flattenedRequestString;
+        AnnotationValidations.validate(android.annotation.NonNull.class, null,
+                mFlattenedRequestString);
+        mCredentialEntries = entries;
+        AnnotationValidations.validate(android.annotation.NonNull.class, null,
+                mCredentialEntries);
+    }
+
+    public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
+            new Parcelable.Creator<CredentialDescription>() {
+                @Override
+                public CredentialDescription createFromParcel(Parcel in) {
+                    return new CredentialDescription(in);
+                }
+
+                @Override
+                public CredentialDescription[] newArray(int size) {
+                    return new CredentialDescription[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mType);
+        dest.writeString(mFlattenedRequestString);
+        dest.writeTypedList(mCredentialEntries, flags);
+    }
+
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    @NonNull
+    public String getFlattenedRequestString() {
+        return mFlattenedRequestString;
+    }
+
+    @NonNull
+    public List<CredentialEntry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mFlattenedRequestString);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return Objects.equals(mType, ((CredentialDescription) obj).getType())
+                && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
+    }
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index d4daf364..e15cec8 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -63,6 +63,14 @@
             "enable_credential_manager";
 
     /**
+     * Flag to enable and disable Credential Description api.
+     *
+     * @hide
+     */
+    private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+            "enable_credential_description_api";
+
+    /**
      * @hide instantiated by ContextImpl.
      */
     public CredentialManager(Context context, ICredentialManager service) {
@@ -82,7 +90,7 @@
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
      */
-    public void executeGetCredential(
+    public void getCredential(
             @NonNull GetCredentialRequest request,
             @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
@@ -94,7 +102,7 @@
         requireNonNull(callback, "callback must not be null");
 
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
-            Log.w(TAG, "executeGetCredential already canceled");
+            Log.w(TAG, "getCredential already canceled");
             return;
         }
 
@@ -126,7 +134,7 @@
      * @param executor the callback will take place on this {@link Executor}
      * @param callback the callback invoked when the request succeeds or fails
      */
-    public void executeCreateCredential(
+    public void createCredential(
             @NonNull CreateCredentialRequest request,
             @NonNull Activity activity,
             @Nullable CancellationSignal cancellationSignal,
@@ -139,7 +147,7 @@
         requireNonNull(callback, "callback must not be null");
 
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
-            Log.w(TAG, "executeCreateCredential already canceled");
+            Log.w(TAG, "createCredential already canceled");
             return;
         }
 
@@ -185,7 +193,7 @@
         requireNonNull(callback, "callback must not be null");
 
         if (cancellationSignal != null && cancellationSignal.isCanceled()) {
-            Log.w(TAG, "executeCreateCredential already canceled");
+            Log.w(TAG, "clearCredentialState already canceled");
             return;
         }
 
@@ -294,6 +302,112 @@
                 true);
     }
 
+    /**
+     * Returns whether the credential description api is enabled.
+     *
+     * @hide
+     */
+    public static boolean isCredentialDescriptionApiEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+    }
+
+    /**
+     *  Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
+     * a CredentialProvider has. This registry will then be used by
+     * {@link #executeGetCredential(GetCredentialRequest, Activity,
+            * CancellationSignal, Executor, OutcomeReceiver)} to determine where to
+     * fetch the requested {@link Credential} from.
+     *
+     *
+     * @param request the request data
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @throws {@link  UnsupportedOperationException} if the feature has not been enabled.
+     *
+     * @hide
+     */
+    public void registerCredentialDescription(
+            @NonNull RegisterCredentialDescriptionRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+
+        if (!isCredentialDescriptionApiEnabled()) {
+            throw new UnsupportedOperationException("This API is not currently supported.");
+        }
+
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.registerCredentialDescription(request,
+                    new RegisterCredentialDescriptionTransport(executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+
+    /**
+     *  Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
+     * that has been registered previously.
+     *
+     *
+     * @param request the request data
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @throws {@link  UnsupportedOperationException} if the feature has not been enabled.
+     *
+     * @hide
+     */
+    public void unRegisterCredentialDescription(
+            @NonNull UnregisterCredentialDescriptionRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+
+        if (!isCredentialDescriptionApiEnabled()) {
+            throw new UnsupportedOperationException("This API is not currently supported.");
+        }
+
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.unRegisterCredentialDescription(request,
+                    new UnregisterCredentialDescriptionTransport(executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
@@ -455,4 +569,54 @@
                     () -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
         }
     }
+
+    private static class RegisterCredentialDescriptionTransport
+            extends IRegisterCredentialDescriptionCallback.Stub {
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, RegisterCredentialDescriptionException> mCallback;
+
+        private RegisterCredentialDescriptionTransport(Executor executor,
+                OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse() {
+            mCallback.onResult(null);
+        }
+
+        @Override
+        public void onError(String errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new RegisterCredentialDescriptionException(errorCode,
+                            message)));
+        }
+    }
+
+    private static class UnregisterCredentialDescriptionTransport
+            extends IUnregisterCredentialDescriptionCallback.Stub {
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, UnregisterCredentialDescriptionException> mCallback;
+
+        private UnregisterCredentialDescriptionTransport(Executor executor,
+                OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse() {
+            mCallback.onResult(null);
+        }
+
+        @Override
+        public void onError(String errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new UnregisterCredentialDescriptionException(errorCode,
+                            message)));
+        }
+    }
 }
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 478afff..720c53b 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -28,7 +28,7 @@
 
 /**
  * Represents an error encountered during the
- * {@link CredentialManager#executeGetCredential(GetCredentialRequest,
+ * {@link CredentialManager#getCredential(GetCredentialRequest,
  * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
  */
 public class GetCredentialException extends Exception {
@@ -41,7 +41,7 @@
 
     /**
      * The error type value for when no credential is found available for the given {@link
-     * CredentialManager#executeGetCredential(GetCredentialRequest, Activity, CancellationSignal,
+     * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
      * Executor, OutcomeReceiver)} request.
      */
     @NonNull
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index 47731dd..55daf86 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -53,7 +53,7 @@
     /**
      * Determines whether the request must only be fulfilled by a system provider.
      */
-    private final boolean mRequireSystemProvider;
+    private final boolean mIsSystemProviderRequired;
 
     /**
      * Returns the requested credential type.
@@ -91,8 +91,8 @@
      * Returns true if the request must only be fulfilled by a system provider, and false
      * otherwise.
      */
-    public boolean requireSystemProvider() {
-        return mRequireSystemProvider;
+    public boolean isSystemProviderRequired() {
+        return mIsSystemProviderRequired;
     }
 
     @Override
@@ -100,7 +100,7 @@
         dest.writeString8(mType);
         dest.writeBundle(mCredentialRetrievalData);
         dest.writeBundle(mCandidateQueryData);
-        dest.writeBoolean(mRequireSystemProvider);
+        dest.writeBoolean(mIsSystemProviderRequired);
     }
 
     @Override
@@ -114,7 +114,7 @@
                 + "type=" + mType
                 + ", requestData=" + mCredentialRetrievalData
                 + ", candidateQueryData=" + mCandidateQueryData
-                + ", requireSystemProvider=" + mRequireSystemProvider
+                + ", isSystemProviderRequired=" + mIsSystemProviderRequired
                 + "}";
     }
 
@@ -125,7 +125,7 @@
      * @param credentialRetrievalData the request data
      * @param candidateQueryData      the partial request data that will be sent to the provider
      *                                during the initial credential candidate query stage
-     * @param requireSystemProvider   whether the request must only be fulfilled by a system
+     * @param isSystemProviderRequired   whether the request must only be fulfilled by a system
      *                                provider
      * @throws IllegalArgumentException If type is empty.
      */
@@ -133,20 +133,20 @@
             @NonNull String type,
             @NonNull Bundle credentialRetrievalData,
             @NonNull Bundle candidateQueryData,
-            boolean requireSystemProvider) {
+            boolean isSystemProviderRequired) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
         mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
                 "requestData must not be null");
         mCandidateQueryData = requireNonNull(candidateQueryData,
                 "candidateQueryData must not be null");
-        mRequireSystemProvider = requireSystemProvider;
+        mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
     private GetCredentialOption(@NonNull Parcel in) {
         String type = in.readString8();
         Bundle data = in.readBundle();
         Bundle candidateQueryData = in.readBundle();
-        boolean requireSystemProvider = in.readBoolean();
+        boolean isSystemProviderRequired = in.readBoolean();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
@@ -154,7 +154,7 @@
         AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
         mCandidateQueryData = candidateQueryData;
         AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
-        mRequireSystemProvider = requireSystemProvider;
+        mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
     public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index c3ca03d..75b3d0c 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -21,10 +21,14 @@
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.os.ICancellationSignal;
 
@@ -44,4 +48,9 @@
     @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
 
     void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
+
+    @nullable ICancellationSignal registerCredentialDescription(in RegisterCredentialDescriptionRequest request, in IRegisterCredentialDescriptionCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal unRegisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, in IUnregisterCredentialDescriptionCallback callback, String callingPackage);
 }
+
diff --git a/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
new file mode 100644
index 0000000..124a319
--- /dev/null
+++ b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IRegisterCredentialDescriptionCallback {
+    oneway void onResponse();
+    oneway void onError(String errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
new file mode 100644
index 0000000..b30a12a
--- /dev/null
+++ b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IUnregisterCredentialDescriptionCallback {
+    oneway void onResponse();
+    oneway void onError(String errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionException.java b/core/java/android/credentials/RegisterCredentialDescriptionException.java
new file mode 100644
index 0000000..3cf5a75
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionException.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class RegisterCredentialDescriptionException extends Exception {
+
+    @NonNull public final String errorType;
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public RegisterCredentialDescriptionException(@NonNull String errorType,
+            @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public RegisterCredentialDescriptionException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType =
+                Preconditions
+                        .checkStringNotEmpty(errorType, "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public RegisterCredentialDescriptionException(@NonNull String errorType,
+            @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public RegisterCredentialDescriptionException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
new file mode 100644
index 0000000..1d56728
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+parcelable RegisterCredentialDescriptionRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.java b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
new file mode 100644
index 0000000..de31279
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A request to register a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}.
+ *
+ * @hide
+ */
+public final class RegisterCredentialDescriptionRequest implements Parcelable {
+
+    public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
+
+    @NonNull
+    private final List<CredentialDescription> mCredentialDescriptions;
+
+    public RegisterCredentialDescriptionRequest(
+            @NonNull CredentialDescription credentialDescription) {
+        mCredentialDescriptions = Arrays.asList(requireNonNull(credentialDescription));
+    }
+
+    public RegisterCredentialDescriptionRequest(
+            @NonNull List<CredentialDescription> credentialDescriptions) {
+        mCredentialDescriptions = new ArrayList<>(requireNonNull(credentialDescriptions));
+    }
+
+    private RegisterCredentialDescriptionRequest(@NonNull Parcel in) {
+        List<CredentialDescription> credentialDescriptions = new ArrayList<>();
+        in.readTypedList(credentialDescriptions, CredentialDescription.CREATOR);
+
+        mCredentialDescriptions = new ArrayList<>();
+        AnnotationValidations.validate(android.annotation.NonNull.class, null,
+                credentialDescriptions);
+        mCredentialDescriptions.addAll(credentialDescriptions);
+    }
+
+    public static final @NonNull Parcelable.Creator<RegisterCredentialDescriptionRequest> CREATOR =
+            new Parcelable.Creator<RegisterCredentialDescriptionRequest>() {
+                @Override
+                public RegisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+                    return new RegisterCredentialDescriptionRequest(in);
+                }
+
+                @Override
+                public RegisterCredentialDescriptionRequest[] newArray(int size) {
+                    return new RegisterCredentialDescriptionRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mCredentialDescriptions, flags);
+    }
+
+    @NonNull
+    public List<CredentialDescription> getCredentialDescriptions() {
+        return mCredentialDescriptions;
+    }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionException.java b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
new file mode 100644
index 0000000..0c786bd
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class UnregisterCredentialDescriptionException extends Exception {
+
+    @NonNull public final String errorType;
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public UnregisterCredentialDescriptionException(@NonNull String errorType,
+            @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public UnregisterCredentialDescriptionException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType =
+                Preconditions
+                        .checkStringNotEmpty(errorType, "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public UnregisterCredentialDescriptionException(@NonNull String errorType,
+            @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link RegisterCredentialDescriptionException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public UnregisterCredentialDescriptionException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
new file mode 100644
index 0000000..e25f13c
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+parcelable UnregisterCredentialDescriptionRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
new file mode 100644
index 0000000..f3454c1
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+
+/**
+ * A request to unregister a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}. *
+ *
+ * @hide
+ */
+public final class UnregisterCredentialDescriptionRequest implements Parcelable {
+
+    @NonNull
+    private final CredentialDescription mCredentialDescription;
+
+    public UnregisterCredentialDescriptionRequest(@NonNull CredentialDescription
+            credentialDescription) {
+        mCredentialDescription = requireNonNull(credentialDescription);
+    }
+
+    private UnregisterCredentialDescriptionRequest(@NonNull Parcel in) {
+        CredentialDescription credentialDescription =
+                CredentialDescription.CREATOR.createFromParcel(in);
+
+        mCredentialDescription = credentialDescription;
+        AnnotationValidations.validate(android.annotation.NonNull.class, null,
+                credentialDescription);
+    }
+
+    public static final @NonNull Parcelable.Creator<UnregisterCredentialDescriptionRequest>
+            CREATOR = new Parcelable.Creator<UnregisterCredentialDescriptionRequest>() {
+                @Override
+                public UnregisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+                    return new UnregisterCredentialDescriptionRequest(in);
+                }
+
+                @Override
+                public UnregisterCredentialDescriptionRequest[] newArray(int size) {
+                    return new UnregisterCredentialDescriptionRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mCredentialDescription.writeToParcel(dest, flags);
+    }
+
+    @NonNull
+    public CredentialDescription getCredentialDescription() {
+        return mCredentialDescription;
+    }
+}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index e7b8dab..49ae9e9 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,9 +48,9 @@
 
     /** Type value for any request that does not require UI. */
     @NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
-    /** Type value for an executeGetCredential request. */
+    /** Type value for a getCredential request. */
     @NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
-    /** Type value for an executeCreateCredential request. */
+    /** Type value for a createCredential request. */
     @NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
 
     /** @hide */
diff --git a/core/java/android/hardware/input/HostUsiVersion.aidl b/core/java/android/hardware/input/HostUsiVersion.aidl
new file mode 100644
index 0000000..74f0ba8
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable HostUsiVersion;
diff --git a/core/java/android/hardware/input/HostUsiVersion.java b/core/java/android/hardware/input/HostUsiVersion.java
new file mode 100644
index 0000000..8f13d75
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Provides information about the supported Universal Stylus Initiative (USI) version of the
+ * host device.
+ *
+ * This holds version information about the host device (e.g. the touchscreen/display), not
+ * the USI version of a stylus.
+ *
+ * @see InputManager#getHostUsiVersion(android.view.Display)
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ */
+@DataClass(genParcelable = true, genHiddenConstructor = true, genToString = true,
+        genEqualsHashCode = true)
+public final class HostUsiVersion implements Parcelable {
+    /**
+     * The major USI version supported by the input device.
+     * For example, if the device supports USI 2.0, this will return 2.
+     */
+    private final int mMajorVersion;
+
+    /**
+     * The minor USI version supported by the input device.
+     * For example, if the device supports USI 2.0, this will return 0.
+     */
+    private final int mMinorVersion;
+
+    /** @hide */
+    public boolean isValid() {
+        return mMajorVersion >= 0 && mMinorVersion >= 0;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/HostUsiVersion.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new HostUsiVersion.
+     *
+     * @param majorVersion
+     *   The major USI version supported by the input device.
+     *   For example, if the device supports USI 2.0, this will return 2.
+     * @param minorVersion
+     *   The minor USI version supported by the input device.
+     *   For example, if the device supports USI 2.0, this will return 0.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public HostUsiVersion(
+            int majorVersion,
+            int minorVersion) {
+        this.mMajorVersion = majorVersion;
+        this.mMinorVersion = minorVersion;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The major USI version supported by the input device.
+     * For example, if the device supports USI 2.0, this will return 2.
+     */
+    @DataClass.Generated.Member
+    public int getMajorVersion() {
+        return mMajorVersion;
+    }
+
+    /**
+     * The minor USI version supported by the input device.
+     * For example, if the device supports USI 2.0, this will return 0.
+     */
+    @DataClass.Generated.Member
+    public int getMinorVersion() {
+        return mMinorVersion;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "HostUsiVersion { " +
+                "majorVersion = " + mMajorVersion + ", " +
+                "minorVersion = " + mMinorVersion +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(HostUsiVersion other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        HostUsiVersion that = (HostUsiVersion) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mMajorVersion == that.mMajorVersion
+                && mMinorVersion == that.mMinorVersion;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mMajorVersion;
+        _hash = 31 * _hash + mMinorVersion;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mMajorVersion);
+        dest.writeInt(mMinorVersion);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ HostUsiVersion(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int majorVersion = in.readInt();
+        int minorVersion = in.readInt();
+
+        this.mMajorVersion = majorVersion;
+        this.mMinorVersion = minorVersion;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<HostUsiVersion> CREATOR
+            = new Parcelable.Creator<HostUsiVersion>() {
+        @Override
+        public HostUsiVersion[] newArray(int size) {
+            return new HostUsiVersion[size];
+        }
+
+        @Override
+        public HostUsiVersion createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new HostUsiVersion(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1673884256908L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/HostUsiVersion.java",
+            inputSignatures = "private final  int mMajorVersion\nprivate final  int mMinorVersion\npublic  boolean isValid()\nclass HostUsiVersion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index fb201cf..6acd8ce 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -56,8 +56,10 @@
 import android.os.VibratorManager;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.util.DisplayUtils;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
@@ -69,6 +71,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
@@ -1584,6 +1587,66 @@
         }
     }
 
+    /**
+     * Reports the version of the Universal Stylus Initiative (USI) protocol supported by the given
+     * display, if any.
+     *
+     * @return the USI version supported by the display, or null if the device does not support USI
+     * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+     */
+    @Nullable
+    public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+        Objects.requireNonNull(display, "display should not be null");
+
+        // Return the first valid USI version reported by any input device associated with
+        // the display.
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            for (int i = 0; i < mInputDevices.size(); i++) {
+                final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+                if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+                    if (device.getHostUsiVersion() != null) {
+                        return device.getHostUsiVersion();
+                    }
+                }
+            }
+        }
+
+        // If there are no input devices that report a valid USI version, see if there is a config
+        // that specifies the USI version for the display. This is to handle cases where the USI
+        // input device is not registered by the kernel/driver all the time.
+        return findConfigUsiVersionForDisplay(display);
+    }
+
+    private HostUsiVersion findConfigUsiVersionForDisplay(@NonNull Display display) {
+        final Context context = Objects.requireNonNull(ActivityThread.currentApplication());
+        final String[] displayUniqueIds = context.getResources().getStringArray(
+                R.array.config_displayUniqueIdArray);
+        final int index;
+        if (displayUniqueIds.length == 0 && display.getDisplayId() == context.getDisplayId()) {
+            index = 0;
+        } else {
+            index = DisplayUtils.getDisplayUniqueIdConfigIndex(context.getResources(),
+                    display.getUniqueId());
+        }
+
+        final String[] versions = context.getResources().getStringArray(
+                R.array.config_displayUsiVersionArray);
+        if (index < 0 || index >= versions.length) {
+            return null;
+        }
+        final String version = versions[index];
+        if (version == null || version.isEmpty()) {
+            return null;
+        }
+        final String[] majorMinor = version.split("\\.");
+        if (majorMinor.length != 2) {
+            throw new IllegalStateException("Failed to parse USI version: " + version);
+        }
+        return new HostUsiVersion(Integer.parseInt(majorMinor[0]), Integer.parseInt(majorMinor[1]));
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index ed6a88f..d55367f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -223,13 +223,11 @@
                 final SomeArgs args = (SomeArgs) msg.obj;
                 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
-                    ImeTracker.forLogging().onProgress(
-                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.showSoftInputWithToken(
                             msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
                 } else {
-                    ImeTracker.forLogging().onFailed(
-                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -238,13 +236,11 @@
                 final SomeArgs args = (SomeArgs) msg.obj;
                 final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
-                    ImeTracker.forLogging().onProgress(
-                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
                             (IBinder) args.arg1, statsToken);
                 } else {
-                    ImeTracker.forLogging().onFailed(
-                            statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -432,7 +428,7 @@
     @Override
     public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
             int flags, ResultReceiver resultReceiver) {
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver, statsToken));
     }
@@ -441,7 +437,7 @@
     @Override
     public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
             int flags, ResultReceiver resultReceiver) {
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
                 flags, hideInputToken, resultReceiver, statsToken));
     }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ee9d8a4..872414a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -896,8 +896,7 @@
         @MainThread
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
-            ImeTracker.forLogging().onProgress(
-                    mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
+            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingHideSoftInput) {
@@ -951,8 +950,7 @@
         @MainThread
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            ImeTracker.forLogging().onProgress(
-                    mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
+            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
             // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -968,11 +966,11 @@
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
-                ImeTracker.forLogging().onProgress(mCurStatsToken,
+                ImeTracker.get().onProgress(mCurStatsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
                 showWindow(true);
             } else {
-                ImeTracker.forLogging().onFailed(mCurStatsToken,
+                ImeTracker.get().onFailed(mCurStatsToken,
                         ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
             }
             setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2981,7 +2979,7 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
-        ImeTracker.forLogging().onProgress(mCurStatsToken,
+        ImeTracker.get().onProgress(mCurStatsToken,
                 ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
                 ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/OWNERS
+++ b/core/java/android/nfc/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 48448
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/core/java/android/nfc/cardemulation/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/cardemulation/OWNERS
+++ b/core/java/android/nfc/cardemulation/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 48448
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
diff --git a/core/java/android/nfc/dta/OWNERS b/core/java/android/nfc/dta/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/dta/OWNERS
+++ b/core/java/android/nfc/dta/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 48448
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
diff --git a/core/java/android/nfc/tech/OWNERS b/core/java/android/nfc/tech/OWNERS
index 6aaf039..9a2e446 100644
--- a/core/java/android/nfc/tech/OWNERS
+++ b/core/java/android/nfc/tech/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 48448
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index a543a2d..b97993a 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -119,6 +119,19 @@
     public static final int FLAG_CONFIRMATION_DIALOG = 0x1;
 
     /**
+     * Flag marking whether corresponding pending report allows consentless bugreport.
+     */
+    public static final int FLAG_ALLOW_CONSENTLESS_BUGREPORT = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            FLAG_CONFIRMATION_DIALOG,
+            FLAG_ALLOW_CONSENTLESS_BUGREPORT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PendingReportFlags {}
+
+    /**
      * Flag marking fields and incident reports than can be taken
      * off the device only via adb.
      */
@@ -220,8 +233,9 @@
         /**
          * Get the flags requested for this pending report.
          *
-         * @see #FLAG_CONFIRMATION_DIALOG
+         * @see PendingReportFlags
          */
+        @PendingReportFlags
         public int getFlags() {
             return mFlags;
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 62d8fb2..9a25c70 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
-import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.IOException;
@@ -2111,17 +2110,6 @@
     }
 
     /**
-     * @hide
-     * @return Whether the device is running with split system user. It means the system user and
-     * primary user are two separate users. Previously system user and primary user are combined as
-     * a single owner user.  see @link {android.os.UserHandle#USER_OWNER}
-     */
-    @TestApi
-    public static boolean isSplitSystemUser() {
-        return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
-    }
-
-    /**
      * @return Whether guest user is always ephemeral
      * @hide
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d113ca0..62b99ba 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1060,8 +1060,13 @@
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
-     * Input: Nothing.
-     * <p>
+     * Input: The optional {@code #EXTRA_EXPLICIT_LOCALES} with language tags that contains locales
+     * to limit available locales. This is only supported when device is under demo mode.
+     * If intent does not contain this extra, it will show system supported locale list.
+     * <br/>
+     * If {@code #EXTRA_EXPLICIT_LOCALES} contain a unsupported locale, it will still show this
+     * locale on list, but may not be supported by the devcie.
+     *
      * Output: Nothing.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1069,6 +1074,18 @@
             "android.settings.LOCALE_SETTINGS";
 
     /**
+     * Activity Extra: Show explicit locales in launched locale picker activity.
+     *
+     * This can be passed as an extra field in an Activity Intent with one or more language tags
+     * as a {@link LocaleList}. This must be passed as an extra field to the
+     * {@link #ACTION_LOCALE_SETTINGS}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_EXPLICIT_LOCALES =
+            "android.provider.extra.EXPLICIT_LOCALES";
+
+    /**
      * Activity Action: Show settings to allow configuration of per application locale.
      * <p>
      * Input: The Intent's data URI can specify the application package name to directly invoke the
@@ -6142,7 +6159,6 @@
             MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED);
             MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED);
             MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON);
-            MOVED_TO_GLOBAL.add(Settings.Global.BUGREPORT_IN_POWER_MENU);
             MOVED_TO_GLOBAL.add(Settings.Global.CDMA_CELL_BROADCAST_SMS);
             MOVED_TO_GLOBAL.add(Settings.Global.CDMA_ROAMING_MODE);
             MOVED_TO_GLOBAL.add(Settings.Global.CDMA_SUBSCRIPTION_MODE);
@@ -6763,14 +6779,26 @@
         /**
          * When the user has enable the option to have a "bug report" command
          * in the power menu.
-         * @deprecated Use {@link android.provider.Settings.Global#BUGREPORT_IN_POWER_MENU} instead
          * @hide
          */
-        @Deprecated
         @Readable
         public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
 
         /**
+         * The package name for the custom bugreport handler app. This app must be bugreport
+         * allow-listed. This is currently used only by Power Menu short press.
+         * @hide
+         */
+        public static final String CUSTOM_BUGREPORT_HANDLER_APP = "custom_bugreport_handler_app";
+
+        /**
+         * The user id for the custom bugreport handler app. This is currently used only by Power
+         * Menu short press.
+         * @hide
+         */
+        public static final String CUSTOM_BUGREPORT_HANDLER_USER = "custom_bugreport_handler_user";
+
+        /**
          * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead
          */
         @Deprecated
@@ -11721,26 +11749,32 @@
         /**
          * When the user has enable the option to have a "bug report" command
          * in the power menu.
+         * @deprecated Use {@link android.provider.Settings.Secure#BUGREPORT_IN_POWER_MENU} instead
          * @hide
          */
+        @Deprecated
         @Readable
         public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
 
         /**
          * The package name for the custom bugreport handler app. This app must be whitelisted.
          * This is currently used only by Power Menu short press.
-         *
+         * @deprecated Use {@link android.provider.Settings.Secure#CUSTOM_BUGREPORT_HANDLER_APP}
+         * instead
          * @hide
          */
+        @Deprecated
         @Readable
         public static final String CUSTOM_BUGREPORT_HANDLER_APP = "custom_bugreport_handler_app";
 
         /**
          * The user id for the custom bugreport handler app. This is currently used only by Power
          * Menu short press.
-         *
+         * @deprecated Use {@link android.provider.Settings.Secure#CUSTOM_BUGREPORT_HANDLER_USER}
+         * instead
          * @hide
          */
+        @Deprecated
         @Readable
         public static final String CUSTOM_BUGREPORT_HANDLER_USER = "custom_bugreport_handler_user";
 
@@ -16364,6 +16398,9 @@
             MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
             MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
             MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
+            MOVED_TO_SECURE.add(Global.BUGREPORT_IN_POWER_MENU);
+            MOVED_TO_SECURE.add(Global.CUSTOM_BUGREPORT_HANDLER_APP);
+            MOVED_TO_SECURE.add(Global.CUSTOM_BUGREPORT_HANDLER_USER);
         }
 
         // Certain settings have been moved from global to the per-user system namespace
@@ -17964,6 +18001,28 @@
             public static final String OEM_SETUP_VERSION = "oem_setup_version";
 
             /**
+             * The key to indicate to Setup Wizard if OEM setup is completed in Wear Services.
+             * @hide
+             */
+            public static final String OEM_SETUP_COMPLETED_STATUS = "oem_setup_completed_status";
+
+             /**
+              * Constant provided to Setup Wizard to inform about failure of OEM setup in Wear
+              * Services. The value should be provided with setting name {@link
+              * #OEM_SETUP_COMPLETED_STATUS}.
+              * @hide
+              */
+            public static final int OEM_SETUP_COMPLETED_FAILURE = 0;
+
+            /**
+             * Constant provided to Setup Wizard to inform about successful completion of OEM setup
+             * in Wear Services. The value should be provided with setting name {@link
+             * #OEM_SETUP_COMPLETED_STATUS}.
+             * @hide
+             */
+            public static final int OEM_SETUP_COMPLETED_SUCCESS = 1;
+
+            /**
              * Controls the gestures feature.
              * @hide
              */
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index b09d2e9..950f9b5 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -16,6 +16,7 @@
 
 package android.service.quickaccesswallet;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -24,28 +25,70 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
 /**
  * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
  * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
- * card image, card image content description, and a {@link PendingIntent} to be used if the user
- * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ * card type, card image, card image content description, and a {@link PendingIntent} to be used if
+ * the user clicks on the card. Cards may be displayed with an icon and label, though these are
+ * optional. Valuable cards will also have a second image that will be displayed when the card is
+ * tapped.
  */
+
 public final class WalletCard implements Parcelable {
 
+    /**
+     * Unknown cards refer to cards whose types are unspecified.
+     */
+    public static final int CARD_TYPE_UNKNOWN = 0;
+
+    /**
+     * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
+     * make cash-equivalent payments.
+     */
+    public static final int CARD_TYPE_PAYMENT = 1;
+
+    /**
+     * Valuable cards refer to any cards that are not used for cash-equivalent payment.
+     * This includes event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
+     */
+    public static final int CARD_TYPE_VALUABLE = 2;
+
     private final String mCardId;
+    private final int mCardType;
     private final Icon mCardImage;
     private final CharSequence mContentDescription;
     private final PendingIntent mPendingIntent;
     private final Icon mCardIcon;
     private final CharSequence mCardLabel;
+    private final Icon mValuableCardSecondaryImage;
 
     private WalletCard(Builder builder) {
         this.mCardId = builder.mCardId;
+        this.mCardType = builder.mCardType;
         this.mCardImage = builder.mCardImage;
         this.mContentDescription = builder.mContentDescription;
         this.mPendingIntent = builder.mPendingIntent;
         this.mCardIcon = builder.mCardIcon;
         this.mCardLabel = builder.mCardLabel;
+        this.mValuableCardSecondaryImage = builder.mValuableCardSecondaryImage;
+    }
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CARD_TYPE_"}, value = {
+            CARD_TYPE_UNKNOWN,
+            CARD_TYPE_PAYMENT,
+            CARD_TYPE_VALUABLE
+    })
+    public @interface CardType {
     }
 
     @Override
@@ -56,29 +99,44 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mCardId);
+        dest.writeInt(mCardType);
         mCardImage.writeToParcel(dest, flags);
         TextUtils.writeToParcel(mContentDescription, dest, flags);
         PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
-        if (mCardIcon == null) {
+        writeIconIfNonNull(mCardIcon, dest, flags);
+        TextUtils.writeToParcel(mCardLabel, dest, flags);
+        writeIconIfNonNull(mValuableCardSecondaryImage, dest, flags);
+
+    }
+
+    /** Utility function called by writeToParcel
+     */
+    private void writeIconIfNonNull(Icon icon,  Parcel dest, int flags) {
+        if (icon == null) {
             dest.writeByte((byte) 0);
         } else {
             dest.writeByte((byte) 1);
-            mCardIcon.writeToParcel(dest, flags);
+            icon.writeToParcel(dest, flags);
         }
-        TextUtils.writeToParcel(mCardLabel, dest, flags);
     }
 
     private static WalletCard readFromParcel(Parcel source) {
         String cardId = source.readString();
+        int cardType = source.readInt();
         Icon cardImage = Icon.CREATOR.createFromParcel(source);
         CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
         Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
         CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-        return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+        Icon valuableCardSecondaryImage = source.readByte() == 0 ? null :
+                Icon.CREATOR.createFromParcel(source);
+        Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
                 .setCardIcon(cardIcon)
-                .setCardLabel(cardLabel)
-                .build();
+                .setCardLabel(cardLabel);
+
+        return cardType == CARD_TYPE_VALUABLE
+                ? builder.setValuableCardSecondaryImage(valuableCardSecondaryImage).build() :
+                 builder.build();
     }
 
     @NonNull
@@ -104,6 +162,15 @@
     }
 
     /**
+     * Returns the card type.
+     */
+    @NonNull
+    @CardType
+    public int getCardType() {
+        return mCardType;
+    }
+
+    /**
      * The visual representation of the card. If the card image Icon is a bitmap, it should have a
      * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
      * GetWalletCardsRequest#getCardHeightPx()}.
@@ -158,23 +225,36 @@
     }
 
     /**
-     * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+    * Visual representation of the card when it is tapped. Includes a barcode to scan the card in
+     * addition to the information in the primary image.
+    */
+    @Nullable
+    public Icon getValuableCardSecondaryImage() {
+        return mValuableCardSecondaryImage;
+    }
+
+    /**
+     * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
      * contentDescription, and pendingIntent. If the card is opaque and should be shown with
      * elevation, set hasShadow to true. cardIcon and cardLabel are optional.
      */
     public static final class Builder {
         private String mCardId;
+        private int mCardType;
         private Icon mCardImage;
         private CharSequence mContentDescription;
         private PendingIntent mPendingIntent;
         private Icon mCardIcon;
         private CharSequence mCardLabel;
+        private Icon mValuableCardSecondaryImage;
 
         /**
          * @param cardId             The card id must be non-null and unique within the list of
          *                           cards returned. <b>Note:
          *                           </b> this card ID should <b>not</b> contain PII (Personally
          *                           Identifiable Information, such as username or email address).
+         * @param cardType           Integer representing the card type. The card type must be
+         *                           non-null. If not provided, it defaults to unknown.
          * @param cardImage          The visual representation of the card. If the card image Icon
          *                           is a bitmap, it should have a width of {@link
          *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -193,15 +273,31 @@
          *                           request device unlock before sending the pending intent. It is
          *                           recommended that the pending intent be immutable (use {@link
          *                           PendingIntent#FLAG_IMMUTABLE}).
+         *
+         */
+        public Builder(@NonNull String cardId,
+                @NonNull @CardType int cardType,
+                @NonNull Icon cardImage,
+                @NonNull CharSequence contentDescription,
+                @NonNull PendingIntent pendingIntent
+        ) {
+            mCardId = cardId;
+            mCardType = cardType;
+            mCardImage = cardImage;
+            mContentDescription = contentDescription;
+            mPendingIntent = pendingIntent;
+        }
+
+        /**
+         * Called when a card type is not provided. Calls {@link
+         * Builder#Builder(String, int, Icon, CharSequence, PendingIntent)} with default card type
          */
         public Builder(@NonNull String cardId,
                 @NonNull Icon cardImage,
                 @NonNull CharSequence contentDescription,
                 @NonNull PendingIntent pendingIntent) {
-            mCardId = cardId;
-            mCardImage = cardImage;
-            mContentDescription = contentDescription;
-            mPendingIntent = pendingIntent;
+            this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
+                    pendingIntent);
         }
 
         /**
@@ -236,6 +332,18 @@
         }
 
         /**
+         * Visual representation of the card when it is tapped. Includes a barcode to scan the card
+         * in addition to the information in the primary image.
+         */
+        @NonNull
+        public Builder setValuableCardSecondaryImage(@Nullable Icon valuableCardSecondaryImage) {
+            Preconditions.checkState(mCardType == CARD_TYPE_VALUABLE,
+                    "This field can only be set on valuable cards");
+            mValuableCardSecondaryImage = valuableCardSecondaryImage;
+            return this;
+        }
+
+        /**
          * Builds a new {@link WalletCard} instance.
          *
          * @return A built response.
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index df739e3..ca4716b 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -95,20 +95,6 @@
     public static final String SERVICE_META_DATA = "android.voice_interaction";
 
     /**
-     * Bundle key used to specify the id when the system prepares to show session. It increases for
-     * each request.
-     * <p>
-     * Type: int
-     * </p>
-     * @see #showSession(Bundle, int)
-     * @see #onPrepareToShowSession(Bundle, int)
-     * @see #onShowSessionFailed(Bundle)
-     * @see VoiceInteractionSession#onShow(Bundle, int)
-     * @see VoiceInteractionSession#show(Bundle, int)
-     */
-    public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
-
-    /**
      * For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
      * service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
      * also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -219,7 +205,7 @@
      * bind the session service.
      *
      * @param args  The arguments that were supplied to {@link #showSession(Bundle, int)}.
-     *              It always includes {@link #KEY_SHOW_SESSION_ID}.
+     *              It always includes {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
      * @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
      * @see #showSession(Bundle, int)
      * @see #onShowSessionFailed(Bundle)
@@ -233,7 +219,7 @@
      * Called when the show session failed. E.g. When the system bound the session service failed.
      *
      * @param args Additional info about the show session attempt that failed. For now, includes
-     *             {@link #KEY_SHOW_SESSION_ID}.
+     *             {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
      * @see #showSession(Bundle, int)
      * @see #onPrepareToShowSession(Bundle, int)
      * @see VoiceInteractionSession#onShow(Bundle, int)
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index d55fede..0d51395 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -169,6 +169,20 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface VoiceInteractionActivityEventType{}
 
+    /**
+     * Bundle key used to specify the id when the system prepares to show session. It increases for
+     * each request.
+     * <p>
+     * Type: int
+     * </p>
+     * @see VoiceInteractionService#showSession(Bundle, int)
+     * @see VoiceInteractionService#onPrepareToShowSession(Bundle, int)
+     * @see VoiceInteractionService#onShowSessionFailed(Bundle)
+     * @see #onShow(Bundle, int)
+     * @see #show(Bundle, int)
+     */
+    public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
@@ -1763,7 +1777,7 @@
      * @param args The arguments that were supplied to
      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
      * Some example keys include : "invocation_type", "invocation_phone_state",
-     * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+     * {@link #KEY_SHOW_SESSION_ID}, "invocation_time_ms",
      * Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
      * in milliseconds of the KeyEvent that triggered Assistant and
      * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
new file mode 100644
index 0000000..fbdaa3d
--- /dev/null
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.style.ReplacementSpan;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.View;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The transformation method used by handwriting insert mode.
+ * This transformation will insert a placeholder string to the original text at the given
+ * offset. And it also provides a highlight range for the newly inserted text and the placeholder
+ * text.
+ *
+ * For example,
+ *   original text: "Hello world"
+ *   insert mode is started at index:  5,
+ *   placeholder text: "\n\n"
+ * The transformed text will be: "Hello\n\n world", and the highlight range will be [5, 7)
+ * including the inserted placeholder text.
+ *
+ * If " abc" is inserted to the original text at index 5,
+ *   the new original text: "Hello abc world"
+ *   the new transformed text: "hello abc\n\n world", and the highlight range will be [5, 11).
+ * @hide
+ */
+public class InsertModeTransformationMethod implements TransformationMethod, TextWatcher {
+    /** The start offset of the highlight range in the original text, inclusive. */
+    private int mStart;
+    /**
+     * The end offset of the highlight range in the original text, exclusive. The placeholder text
+     * is also inserted at this index.
+     */
+    private int mEnd;
+    /** The transformation method that's already set on the {@link android.widget.TextView}. */
+    private final TransformationMethod mOldTransformationMethod;
+    /** Whether the {@link android.widget.TextView} is single-lined. */
+    private final boolean mSingleLine;
+
+    /**
+     * @param offset the original offset to start the insert mode. It must be in the range from 0
+     *               to the length of the transformed text.
+     * @param singleLine whether the text is single line.
+     * @param oldTransformationMethod the old transformation method at the
+     * {@link android.widget.TextView}. If it's not null, this {@link TransformationMethod} will
+     * first call {@link TransformationMethod#getTransformation(CharSequence, View)} on the old one,
+     * and then do the transformation for the insert mode.
+     *
+     */
+    public InsertModeTransformationMethod(@IntRange(from = 0) int offset, boolean singleLine,
+            @NonNull TransformationMethod oldTransformationMethod) {
+        mStart = offset;
+        mEnd = offset;
+        mSingleLine = singleLine;
+        mOldTransformationMethod = oldTransformationMethod;
+    }
+
+    public TransformationMethod getOldTransformationMethod() {
+        return mOldTransformationMethod;
+    }
+
+    private CharSequence getPlaceholderText(View view) {
+        if (!mSingleLine) {
+            return  "\n\n";
+        }
+        final SpannableString singleLinePlaceholder = new SpannableString("\uFFFD");
+        final DisplayMetrics displayMetrics = view.getResources().getDisplayMetrics();
+        final int widthPx = (int) Math.ceil(
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 108, displayMetrics));
+
+        singleLinePlaceholder.setSpan(new SingleLinePlaceholderSpan(widthPx), 0, 1,
+                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return singleLinePlaceholder;
+    }
+
+    @Override
+    public CharSequence getTransformation(CharSequence source, View view) {
+        final CharSequence charSequence;
+        if (mOldTransformationMethod != null) {
+            charSequence = mOldTransformationMethod.getTransformation(source, view);
+            if (source instanceof Spannable) {
+                final Spannable spannable = (Spannable) source;
+                spannable.setSpan(mOldTransformationMethod, 0, spannable.length(),
+                        Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+        } else {
+            charSequence = source;
+        }
+
+        final CharSequence placeholderText = getPlaceholderText(view);
+        return new TransformedText(charSequence, placeholderText);
+    }
+
+    @Override
+    public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction,
+            Rect previouslyFocusedRect) {
+        if (mOldTransformationMethod != null) {
+            mOldTransformationMethod.onFocusChanged(view, sourceText, focused, direction,
+                    previouslyFocusedRect);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        // The text change is after the offset where placeholder is inserted, return.
+        if (start > mEnd) return;
+        final int diff = count - before;
+
+        // Note: If start == mStart and before == 0, the change is also considered after the
+        // highlight start. It won't modify the mStart in this case.
+        if (start < mStart) {
+            if (start + before <= mStart) {
+                // The text change is before the highlight start, move the highlight start.
+                mStart += diff;
+            } else {
+                // The text change covers the highlight start. Extend the highlight start to the
+                // change start. This should be a rare case.
+                mStart = start;
+            }
+        }
+
+        if (start + before <= mEnd) {
+            // The text change is before the highlight end, move the highlight end.
+            mEnd += diff;
+        } else if (start < mEnd) {
+            // The text change covers the highlight end. Extend the highlight end to the
+            // change end. This should be a rare case.
+            mEnd = start + count;
+        }
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) { }
+
+    /**
+     * The transformed text returned by the {@link InsertModeTransformationMethod}.
+     */
+    public class TransformedText implements OffsetMapping, Spanned {
+        private final CharSequence mOriginal;
+        private final CharSequence mPlaceholder;
+        private final Spanned mSpannedOriginal;
+        private final Spanned mSpannedPlaceholder;
+
+        TransformedText(CharSequence original, CharSequence placeholder) {
+            mOriginal = original;
+            if (original instanceof Spanned) {
+                mSpannedOriginal = (Spanned) original;
+            } else {
+                mSpannedOriginal = null;
+            }
+            mPlaceholder = placeholder;
+            if (placeholder instanceof Spanned) {
+                mSpannedPlaceholder = (Spanned) placeholder;
+            } else {
+                mSpannedPlaceholder = null;
+            }
+        }
+
+        @Override
+        public int originalToTransformed(int offset, int strategy) {
+            if (offset < 0) return offset;
+            Preconditions.checkArgumentInRange(offset, 0, mOriginal.length(), "offset");
+            if (offset == mEnd && strategy == OffsetMapping.MAP_STRATEGY_CURSOR) {
+                // The offset equals to mEnd. For a cursor position it's considered before the
+                // inserted placeholder text.
+                return offset;
+            }
+            if (offset < mEnd) {
+                return offset;
+            }
+            return offset + mPlaceholder.length();
+        }
+
+        @Override
+        public int transformedToOriginal(int offset, int strategy) {
+            if (offset < 0) return offset;
+            Preconditions.checkArgumentInRange(offset, 0, length(), "offset");
+
+            // The placeholder text is inserted at mEnd. Because the offset is smaller than
+            // mEnd, we can directly return it.
+            if (offset < mEnd) return offset;
+            if (offset < mEnd + mPlaceholder.length()) {
+                return mEnd;
+            }
+            return offset - mPlaceholder.length();
+        }
+
+        @Override
+        public void originalToTransformed(TextUpdate textUpdate) {
+            if (textUpdate.where > mEnd) {
+                textUpdate.where += mPlaceholder.length();
+            } else if (textUpdate.where + textUpdate.before > mEnd) {
+                // The update also covers the placeholder string.
+                textUpdate.before += mPlaceholder.length();
+                textUpdate.after += mPlaceholder.length();
+            }
+        }
+
+        @Override
+        public int length() {
+            return mOriginal.length() + mPlaceholder.length();
+        }
+
+        @Override
+        public char charAt(int index) {
+            Preconditions.checkArgumentInRange(index, 0, length() - 1, "index");
+            if (index < mEnd) {
+                return mOriginal.charAt(index);
+            }
+            if (index < mEnd + mPlaceholder.length()) {
+                return mPlaceholder.charAt(index - mEnd);
+            }
+            return mOriginal.charAt(index - mPlaceholder.length());
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            if (end < start || start < 0 || end > length()) {
+                throw new IndexOutOfBoundsException();
+            }
+            if (start == end) {
+                return "";
+            }
+
+            final int placeholderLength = mPlaceholder.length();
+
+            final int seg1Start = Math.min(start, mEnd);
+            final int seg1End = Math.min(end, mEnd);
+
+            final int seg2Start = MathUtils.constrain(start - mEnd, 0, placeholderLength);
+            final int seg2End = MathUtils.constrain(end - mEnd, 0, placeholderLength);
+
+            final int seg3Start = Math.max(start - placeholderLength, mEnd);
+            final int seg3End = Math.max(end - placeholderLength, mEnd);
+
+            return TextUtils.concat(
+                    mOriginal.subSequence(seg1Start, seg1End),
+                    mPlaceholder.subSequence(seg2Start, seg2End),
+                    mOriginal.subSequence(seg3Start, seg3End));
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mOriginal.subSequence(0, mEnd))
+                    + mPlaceholder
+                    + mOriginal.subSequence(mEnd, mOriginal.length());
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> T[] getSpans(int start, int end, Class<T> type) {
+            if (end < start) {
+                return ArrayUtils.emptyArray(type);
+            }
+
+            final T[] spansOriginal;
+            if (mSpannedOriginal != null) {
+                final int originalStart =
+                        transformedToOriginal(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+                final int originalEnd =
+                        transformedToOriginal(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+                spansOriginal = mSpannedOriginal.getSpans(originalStart, originalEnd, type);
+            } else {
+                spansOriginal = null;
+            }
+
+            final T[] spansPlaceholder;
+            if (mSpannedPlaceholder != null
+                    && intersect(start, end, mEnd, mEnd + mPlaceholder.length())) {
+                final int placeholderStart = Math.max(start - mEnd, 0);
+                final int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length());
+                spansPlaceholder =
+                        mSpannedPlaceholder.getSpans(placeholderStart, placeholderEnd, type);
+            } else {
+                spansPlaceholder = null;
+            }
+
+            // TODO: sort the spans based on their priority.
+            return ArrayUtils.concat(type, spansOriginal, spansPlaceholder);
+        }
+
+        @Override
+        public int getSpanStart(Object tag) {
+            if (mSpannedOriginal != null) {
+                final int index = mSpannedOriginal.getSpanStart(tag);
+                if (index >= 0) {
+                    if (index < mEnd) {
+                        return index;
+                    }
+                    return index + mPlaceholder.length();
+                }
+            }
+
+            // The span is not on original text, try find it on the placeholder.
+            if (mSpannedPlaceholder != null) {
+                final int index = mSpannedPlaceholder.getSpanStart(tag);
+                if (index >= 0) {
+                    // Find the span on placeholder, transform it and return.
+                    return index + mEnd;
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public int getSpanEnd(Object tag) {
+            if (mSpannedOriginal != null) {
+                final int index = mSpannedOriginal.getSpanEnd(tag);
+                if (index >= 0) {
+                    if (index <= mEnd) {
+                        return index;
+                    }
+                    return index + mPlaceholder.length();
+                }
+            }
+
+            // The span is not on original text, try find it on the placeholder.
+            if (mSpannedPlaceholder != null) {
+                final int index = mSpannedPlaceholder.getSpanEnd(tag);
+                if (index >= 0) {
+                    // Find the span on placeholder, transform it and return.
+                    return index + mEnd;
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public int getSpanFlags(Object tag) {
+            if (mSpannedOriginal != null) {
+                final int flags = mSpannedOriginal.getSpanFlags(tag);
+                if (flags != 0) {
+                    return flags;
+                }
+            }
+            if (mSpannedPlaceholder != null) {
+                return mSpannedPlaceholder.getSpanFlags(tag);
+            }
+            return 0;
+        }
+
+        @Override
+        public int nextSpanTransition(int start, int limit, Class type) {
+            if (limit <= start) return limit;
+            final Object[] spans = getSpans(start, limit, type);
+            for (int i = 0; i < spans.length; ++i) {
+                int spanStart = getSpanStart(spans[i]);
+                int spanEnd = getSpanEnd(spans[i]);
+                if (start < spanStart && spanStart < limit) {
+                    limit = spanStart;
+                }
+                if (start < spanEnd && spanEnd < limit) {
+                    limit = spanEnd;
+                }
+            }
+            return limit;
+        }
+
+        /**
+         * Return the start index of the highlight range for the insert mode, inclusive.
+         */
+        public int getHighlightStart() {
+            return mStart;
+        }
+
+        /**
+         * Return the end index of the highlight range for the insert mode, exclusive.
+         */
+        public int getHighlightEnd() {
+            return mEnd + mPlaceholder.length();
+        }
+    }
+
+    /**
+     * The placeholder span used for single line
+     */
+    public static class SingleLinePlaceholderSpan extends ReplacementSpan {
+        private final int mWidth;
+        SingleLinePlaceholderSpan(int width) {
+            mWidth = width;
+        }
+        @Override
+        public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+                @Nullable Paint.FontMetricsInt fm) {
+            return mWidth;
+        }
+
+        @Override
+        public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
+                int top, int y, int bottom, @NonNull Paint paint) { }
+    }
+
+    /**
+     * Return true if the given two ranges intersects. This logic is the same one used in
+     * {@link Spanned} to determine whether a span range intersect with the query range.
+     */
+    private static boolean intersect(int s1, int e1, int s2, int e2) {
+        if (s1 > e2) return false;
+        if (e1 < s2) return false;
+        if (s1 != e1 && s2 != e2) {
+            if (s1 == e2) return false;
+            if (e1 == s2) return false;
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 3e84153..e89be47 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -123,7 +123,7 @@
 
         // TODO: ResultReceiver for IME.
         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
-        ImeTracker.forLogging().onProgress(statsToken,
+        ImeTracker.get().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
 
         if (getControl() == null) {
@@ -164,13 +164,12 @@
         //  - we do already have one, but we have control and use the passed in token
         //      for the insets animation already.
         if (statsToken == null || getControl() != null) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
-                    Process.myUid(),
+            statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
                     ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
 
-        ImeTracker.forLogging().onProgress(statsToken,
+        ImeTracker.get().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
 
         getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 8683cc2..47da3f6 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.hardware.BatteryState;
 import android.hardware.SensorManager;
+import android.hardware.input.HostUsiVersion;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.lights.LightsManager;
@@ -83,7 +84,8 @@
     private final boolean mHasButtonUnderPad;
     private final boolean mHasSensor;
     private final boolean mHasBattery;
-    private final boolean mSupportsUsi;
+    private final HostUsiVersion mHostUsiVersion;
+    private final int mAssociatedDisplayId;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
     @GuardedBy("mMotionRanges")
@@ -467,7 +469,8 @@
             int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
             KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
             @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone,
-            boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
+            boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor,
+            int usiVersionMinor, int associatedDisplayId) {
         mId = id;
         mGeneration = generation;
         mControllerNumber = controllerNumber;
@@ -493,7 +496,8 @@
         mHasSensor = hasSensor;
         mHasBattery = hasBattery;
         mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
-        mSupportsUsi = supportsUsi;
+        mHostUsiVersion = new HostUsiVersion(usiVersionMajor, usiVersionMinor);
+        mAssociatedDisplayId = associatedDisplayId;
     }
 
     private InputDevice(Parcel in) {
@@ -515,7 +519,8 @@
         mHasButtonUnderPad = in.readInt() != 0;
         mHasSensor = in.readInt() != 0;
         mHasBattery = in.readInt() != 0;
-        mSupportsUsi = in.readInt() != 0;
+        mHostUsiVersion = HostUsiVersion.CREATOR.createFromParcel(in);
+        mAssociatedDisplayId = in.readInt();
         mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
 
         int numRanges = in.readInt();
@@ -554,7 +559,8 @@
         private boolean mHasBattery = false;
         private String mKeyboardLanguageTag = null;
         private String mKeyboardLayoutType = null;
-        private boolean mSupportsUsi = false;
+        private int mUsiVersionMajor = -1;
+        private int mUsiVersionMinor = -1;
         private List<MotionRange> mMotionRanges = new ArrayList<>();
 
         /** @see InputDevice#getId() */
@@ -665,9 +671,10 @@
             return this;
         }
 
-        /** @see InputDevice#supportsUsi() () */
-        public Builder setSupportsUsi(boolean supportsUsi) {
-            mSupportsUsi = supportsUsi;
+        /** @see InputDevice#getHostUsiVersion() */
+        public Builder setUsiVersion(@Nullable HostUsiVersion usiVersion) {
+            mUsiVersionMajor = usiVersion != null ? usiVersion.getMajorVersion() : -1;
+            mUsiVersionMinor = usiVersion != null ? usiVersion.getMinorVersion() : -1;
             return this;
         }
 
@@ -699,7 +706,9 @@
                     mHasButtonUnderPad,
                     mHasSensor,
                     mHasBattery,
-                    mSupportsUsi);
+                    mUsiVersionMajor,
+                    mUsiVersionMinor,
+                    Display.INVALID_DISPLAY);
 
             final int numRanges = mMotionRanges.size();
             for (int i = 0; i < numRanges; i++) {
@@ -1276,12 +1285,22 @@
     }
 
     /**
-     * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for
-     * styluses.
+     * Reports the version of the Universal Stylus Initiative (USI) protocol supported by this
+     * input device.
+     *
+     * @return the supported USI version, or null if the device does not support USI
+     * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+     * @see InputManager#getHostUsiVersion(int)
      * @hide
      */
-    public boolean supportsUsi() {
-        return mSupportsUsi;
+    @Nullable
+    public HostUsiVersion getHostUsiVersion() {
+        return mHostUsiVersion.isValid() ? mHostUsiVersion : null;
+    }
+
+    /** @hide */
+    public int getAssociatedDisplayId() {
+        return mAssociatedDisplayId;
     }
 
     /**
@@ -1415,7 +1434,8 @@
         out.writeInt(mHasButtonUnderPad ? 1 : 0);
         out.writeInt(mHasSensor ? 1 : 0);
         out.writeInt(mHasBattery ? 1 : 0);
-        out.writeInt(mSupportsUsi ? 1 : 0);
+        mHostUsiVersion.writeToParcel(out, flags);
+        out.writeInt(mAssociatedDisplayId);
 
         int numRanges = mMotionRanges.size();
         numRanges = numRanges > MAX_RANGES ? MAX_RANGES : numRanges;
@@ -1468,7 +1488,7 @@
 
         description.append("  Has mic: ").append(mHasMicrophone).append("\n");
 
-        description.append("  Supports USI: ").append(mSupportsUsi).append("\n");
+        description.append("  USI Version: ").append(getHostUsiVersion()).append("\n");
 
         if (mKeyboardLanguageTag != null) {
             description.append(" Keyboard language tag: ").append(mKeyboardLanguageTag).append(
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c992450..8e8e28a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -26,7 +26,6 @@
 import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.ime;
-import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
@@ -36,8 +35,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityThread;
-import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -63,7 +60,6 @@
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
 import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.ImeTracker.InputMethodJankContext;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -190,14 +186,6 @@
         @Nullable
         String getRootViewTitle();
 
-        /**
-         * @return the context related to the rootView.
-         */
-        @Nullable
-        default Context getRootViewContext() {
-            return null;
-        }
-
         /** @see ViewRootImpl#dipToPx */
         int dipToPx(int dips);
 
@@ -318,7 +306,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
             ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
-    public @interface AnimationType {
+    @interface AnimationType {
     }
 
     /**
@@ -333,27 +321,6 @@
     /** Logging listener. */
     private WindowInsetsAnimationControlListener mLoggingListener;
 
-    /** Context for {@link android.view.inputmethod.ImeTracker.ImeJankTracker} to monitor jank. */
-    private final InputMethodJankContext mJankContext = new InputMethodJankContext() {
-        @Override
-        public Context getDisplayContext() {
-            return mHost != null ? mHost.getRootViewContext() : null;
-        }
-
-        @Override
-        public SurfaceControl getTargetSurfaceControl() {
-            final InsetsSourceConsumer imeSourceConsumer = mSourceConsumers.get(ITYPE_IME);
-            final InsetsSourceControl imeSourceControl =
-                    imeSourceConsumer != null ? imeSourceConsumer.getControl() : null;
-            return imeSourceControl != null ? imeSourceControl.getLeash() : null;
-        }
-
-        @Override
-        public String getHostPackageName() {
-            return mHost != null ? mHost.getRootViewContext().getPackageName() : null;
-        }
-    };
-
     /**
      * The default implementation of listener, to be used by InsetsController and InsetsPolicy to
      * animate insets.
@@ -371,7 +338,6 @@
         private final boolean mDisable;
         private final int mFloatingImeBottomInset;
         private final WindowInsetsAnimationControlListener mLoggingListener;
-        private final InputMethodJankContext mInputMethodJankContext;
 
         private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
                 new ThreadLocal<AnimationHandler>() {
@@ -385,8 +351,7 @@
 
         public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
                 @InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
-                int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
-                @Nullable InputMethodJankContext jankContext) {
+                int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
             mShow = show;
             mHasAnimationCallbacks = hasAnimationCallbacks;
             mRequestedTypes = requestedTypes;
@@ -395,7 +360,6 @@
             mDisable = disable;
             mFloatingImeBottomInset = floatingImeBottomInset;
             mLoggingListener = loggingListener;
-            mInputMethodJankContext = jankContext;
         }
 
         @Override
@@ -442,26 +406,10 @@
                         + insetsFraction);
             });
             mAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onRequestAnimation(
-                            mInputMethodJankContext,
-                            mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
-                            !mHasAnimationCallbacks);
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onCancelAnimation();
-                }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationFinish();
-                    if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onFinishAnimation();
                 }
             });
             if (!mHasAnimationCallbacks) {
@@ -925,7 +873,7 @@
 
     /**
      * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
-     *      int, android.util.SparseIntArray)
+     *      int, SparseIntArray)
      */
     @VisibleForTesting
     public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -1033,7 +981,7 @@
     public void show(@InsetsType int types) {
         ImeTracker.Token statsToken = null;
         if ((types & ime()) != 0) {
-            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
         }
@@ -1058,9 +1006,6 @@
         }
         // Handle pending request ready in case there was one set.
         if (fromIme && mPendingImeControlRequest != null) {
-            if ((types & Type.ime()) != 0) {
-                ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
-            }
             handlePendingControlRequest(statsToken);
             return;
         }
@@ -1083,7 +1028,7 @@
                         "show ignored for type: %d animType: %d requestedVisible: %s",
                         type, animationType, requestedVisible));
                 if (isImeAnimation) {
-                    ImeTracker.forLogging().onCancelled(statsToken,
+                    ImeTracker.get().onCancelled(statsToken,
                             ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
@@ -1091,21 +1036,16 @@
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
                 if (isImeAnimation) {
-                    ImeTracker.forLogging().onFailed(
-                            statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
             }
             if (isImeAnimation) {
-                ImeTracker.forLogging().onProgress(
-                        statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
             }
             typesReady |= type;
         }
         if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
-        if (fromIme && (typesReady & Type.ime()) != 0) {
-            ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
-        }
         applyAnimation(typesReady, true /* show */, fromIme, statsToken);
     }
 
@@ -1134,7 +1074,7 @@
     public void hide(@InsetsType int types) {
         ImeTracker.Token statsToken = null;
         if ((types & ime()) != 0) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
@@ -1183,14 +1123,13 @@
                 // no-op: already hidden or animating out (because window visibility is
                 // applied before starting animation).
                 if (isImeAnimation) {
-                    ImeTracker.forLogging().onCancelled(statsToken,
+                    ImeTracker.get().onCancelled(statsToken,
                             ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
                 }
                 continue;
             }
             if (isImeAnimation) {
-                ImeTracker.forLogging().onProgress(
-                        statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
             }
             typesReady |= type;
         }
@@ -1262,19 +1201,8 @@
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
-            final boolean monitoredAnimation =
-                    animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
-            if (monitoredAnimation && (types & Type.ime()) != 0) {
-                if (animationType == ANIMATION_TYPE_SHOW) {
-                    ImeTracker.forLatency().onShowCancelled(statsToken,
-                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
-                } else {
-                    ImeTracker.forLatency().onHideCancelled(statsToken,
-                            PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
-                }
-            }
             throw new IllegalStateException("Cannot start a new insets animation of "
                     + Type.toString(types)
                     + " while an existing " + Type.toString(mTypesBeingCancelled)
@@ -1286,7 +1214,7 @@
             types &= ~mDisabledUserAnimationInsetsTypes;
 
             if ((disabledTypes & ime()) != 0) {
-                ImeTracker.forLogging().onFailed(statsToken,
+                ImeTracker.get().onFailed(statsToken,
                         ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
 
                 if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
@@ -1307,8 +1235,7 @@
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
-        ImeTracker.forLogging().onProgress(statsToken,
-                ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
 
@@ -1375,11 +1302,8 @@
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
                     mHost.getInputMethodManager(), null /* icProto */);
-            if (animationType == ANIMATION_TYPE_HIDE) {
-                ImeTracker.forLatency().onHidden(statsToken, ActivityThread::currentApplication);
-            }
         }
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
         mRunningAnimations.add(new RunningAnimation(runner, animationType));
         if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
                 + useInsetsAnimationThread);
@@ -1419,8 +1343,7 @@
     private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
             SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
             @Nullable ImeTracker.Token statsToken) {
-        ImeTracker.forLogging().onProgress(statsToken,
-                ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
 
         int typesReady = 0;
         boolean imeReady = true;
@@ -1523,13 +1446,13 @@
         }
         final ImeTracker.Token statsToken = runner.getStatsToken();
         if (shown) {
-            ImeTracker.forLogging().onProgress(statsToken,
+            ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
-            ImeTracker.forLogging().onShown(statsToken);
+            ImeTracker.get().onShown(statsToken);
         } else {
-            ImeTracker.forLogging().onProgress(statsToken,
+            ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
-            ImeTracker.forLogging().onHidden(statsToken);
+            ImeTracker.get().onHidden(statsToken);
         }
         reportRequestedVisibleTypes();
     }
@@ -1555,13 +1478,13 @@
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
         if (invokeCallback) {
-            ImeTracker.forLogging().onCancelled(control.getStatsToken(),
-                    PHASE_CLIENT_ANIMATION_CANCEL);
+            ImeTracker.get().onCancelled(control.getStatsToken(),
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
             control.cancel();
         } else {
             // Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
-            ImeTracker.forLogging().onProgress(control.getStatsToken(),
-                    PHASE_CLIENT_ANIMATION_CANCEL);
+            ImeTracker.get().onProgress(control.getStatsToken(),
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
         }
         if (DEBUG) {
             Log.d(TAG, TextUtils.formatSimple(
@@ -1726,7 +1649,7 @@
         final InternalAnimationControlListener listener = new InternalAnimationControlListener(
                 show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
                 skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
-                mLoggingListener, mJankContext);
+                mLoggingListener);
 
         // We are about to playing the default animation (show/hide). Passing a null frame indicates
         // the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2763607..175c76b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -70,6 +70,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2781,7 +2782,7 @@
      * TODO(b/260382739): Apply this to all windows.
      */
     private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
-        return lp.type == TYPE_NOTIFICATION_SHADE;
+        return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
     }
 
     private Rect getWindowBoundsInsetSystemBars() {
@@ -5764,7 +5765,7 @@
                 }
                 case MSG_SHOW_INSETS: {
                     final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
-                    ImeTracker.forLogging().onProgress(statsToken,
+                    ImeTracker.get().onProgress(statsToken,
                             ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
                     if (mView == null) {
                         Log.e(TAG,
@@ -5777,7 +5778,7 @@
                 }
                 case MSG_HIDE_INSETS: {
                     final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
-                    ImeTracker.forLogging().onProgress(statsToken,
+                    ImeTracker.get().onProgress(statsToken,
                             ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
                     mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
@@ -10324,10 +10325,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
                 viewAncestor.showInsets(types, fromIme, statsToken);
             } else {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
             }
         }
 
@@ -10341,10 +10342,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
                 viewAncestor.hideInsets(types, fromIme, statsToken);
             } else {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
             }
         }
 
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 037ce87..c59d83e 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -21,7 +21,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
 
 import android.annotation.NonNull;
-import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.os.Handler;
 import android.os.IBinder;
@@ -247,11 +246,6 @@
     }
 
     @Override
-    public Context getRootViewContext() {
-        return mViewRoot != null ? mViewRoot.getView().getContext() : null;
-    }
-
-    @Override
     public int dipToPx(int dips) {
         if (mViewRoot != null) {
             return mViewRoot.dipToPx(dips);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 35f1787..e437c1f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2663,6 +2663,15 @@
         public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
 
         /**
+         * Flag to indicate that the view hierarchy of the window can only be measured when
+         * necessary. If a window size can be known by the LayoutParams, we can use the size to
+         * relayout window, and we don't have to measure the view hierarchy before laying out the
+         * views. This reduces the chances to perform measure.
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+
+        /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
@@ -2864,6 +2873,7 @@
                 PRIVATE_FLAG_NO_MOVE_ANIMATION,
                 PRIVATE_FLAG_COMPATIBLE_WINDOW,
                 PRIVATE_FLAG_SYSTEM_ERROR,
+                PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                 PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -2924,6 +2934,10 @@
                         equals = PRIVATE_FLAG_SYSTEM_ERROR,
                         name = "SYSTEM_ERROR"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        equals = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        name = "OPTIMIZE_MEASURE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 2ad01ed..b5764c5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1235,6 +1235,42 @@
     }
 
     /**
+     * Called when the virtual views are ready to the user for autofill.
+     *
+     * This method is used to notify autofill system the views are ready to the user. And then
+     * Autofill can do initialization if needed before the user starts to input. For example, do
+     * a pre-fill request for the
+     * <a href="/reference/android/service/autofill/Dataset.html#FillDialogUI">fill dialog</a>.
+     *
+     * @param view the host view that holds a virtual view hierarchy.
+     * @param infos extra information for the virtual views. The key is virtual id which represents
+     *             the virtual view in the host view.
+     *
+     * @throws IllegalArgumentException if the {@code infos} was empty
+     */
+    public void notifyVirtualViewsReady(
+            @NonNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos) {
+        Objects.requireNonNull(infos);
+        if (infos.size() == 0) {
+            throw new IllegalArgumentException("No VirtualViewInfo found");
+        }
+        if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
+                && view.isCredential()) {
+            if (sDebug) {
+                Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+                        + view.getAutofillId().toString());
+            }
+            return;
+        }
+        for (int i = 0; i < infos.size(); i++) {
+            final VirtualViewFillInfo info = infos.valueAt(i);
+            final int virtualId = infos.indexOfKey(i);
+            notifyViewReadyInner(getAutofillId(view, virtualId),
+                    (info == null) ? null : info.getAutofillHints());
+        }
+    }
+
+    /**
      * The {@link AutofillFeatureFlags#DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or
      * the view have the allowed autofill hints, performs a fill request to know there is any field
      * supported fill dialog.
@@ -1245,9 +1281,6 @@
         if (sDebug) {
             Log.d(TAG, "notifyViewEnteredForFillDialog:" + v.getAutofillId());
         }
-        if (!hasAutofillFeature()) {
-            return;
-        }
         if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
                 && v.isCredential()) {
             if (sDebug) {
@@ -1256,6 +1289,13 @@
             }
             return;
         }
+        notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
+    }
+
+    private void notifyViewReadyInner(AutofillId id, String[] autofillHints) {
+        if (!hasAutofillFeature()) {
+            return;
+        }
 
         synchronized (mLock) {
             if (mTrackedViews != null) {
@@ -1263,7 +1303,7 @@
                 // different pages but in the same Activity. We need to reset the
                 // mIsFillRequested flag to allow asking for a new FillRequest when
                 // user switches to other page
-                mTrackedViews.checkViewState(v.getAutofillId());
+                mTrackedViews.checkViewState(id);
             }
         }
 
@@ -1273,9 +1313,9 @@
         }
 
         if (mIsFillDialogEnabled
-                || ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) {
+                || ArrayUtils.containsAny(autofillHints, mFillDialogEnabledHints)) {
             if (sDebug) {
-                Log.d(TAG, "Trigger fill request at view entered");
+                Log.d(TAG, "Trigger fill request when the view is ready.");
             }
 
             int flags = FLAG_SUPPORTS_FILL_DIALOG;
diff --git a/core/java/android/view/autofill/VirtualViewFillInfo.java b/core/java/android/view/autofill/VirtualViewFillInfo.java
new file mode 100644
index 0000000..4bec5a4
--- /dev/null
+++ b/core/java/android/view/autofill/VirtualViewFillInfo.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.autofill;
+
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.view.View;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * Information for the virtual view to the autofill framework.
+ */
+@DataClass(genBuilder = true)
+public final class VirtualViewFillInfo {
+
+    /**
+     * Autofill hints of the virtual view.
+     *
+     * @see View#setAutofillHints(String...)
+     */
+    @Nullable
+    @SuppressLint("NullableCollection")
+    private String[] mAutofillHints;
+
+    private static String[] defaultAutofillHints() {
+        return null;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/autofill/VirtualViewFillInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ VirtualViewFillInfo(
+            @Nullable @SuppressLint("NullableCollection") String[] autofillHints) {
+        this.mAutofillHints = autofillHints;
+        com.android.internal.util.AnnotationValidations.validate(
+                SuppressLint.class, null, mAutofillHints,
+                "value", "NullableCollection");
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Autofill hints of the virtual view.
+     *
+     * @see View#setAutofillHints(String...)
+     */
+    @DataClass.Generated.Member
+    public @Nullable @SuppressLint("NullableCollection") String[] getAutofillHints() {
+        return mAutofillHints;
+    }
+
+    /**
+     * A builder for {@link VirtualViewFillInfo}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @Nullable @SuppressLint("NullableCollection") String[] mAutofillHints;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Autofill hints of the virtual view.
+         *
+         * @see View#setAutofillHints(String...)
+         */
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull Builder setAutofillHints(@SuppressLint("NullableCollection") @android.annotation.NonNull String... value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mAutofillHints = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @android.annotation.NonNull VirtualViewFillInfo build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mAutofillHints = defaultAutofillHints();
+            }
+            VirtualViewFillInfo o = new VirtualViewFillInfo(
+                    mAutofillHints);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x2) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1674023010954L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/autofill/VirtualViewFillInfo.java",
+            inputSignatures = "private @android.annotation.Nullable @android.annotation.SuppressLint java.lang.String[] mAutofillHints\nprivate static  java.lang.String[] defaultAutofillHints()\nclass VirtualViewFillInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 9ebaa67..4e5cec7 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -568,6 +568,8 @@
                 supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE;
             } else if (gesture.equals(InsertGesture.class)) {
                 supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT;
+            } else if (gesture.equals(InsertModeGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT_MODE;
             } else if (gesture.equals(DeleteGesture.class)) {
                 supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE;
             } else if (gesture.equals(DeleteRangeGesture.class)) {
@@ -611,6 +613,10 @@
                 == HandwritingGesture.GESTURE_TYPE_INSERT) {
             list.add(InsertGesture.class);
         }
+        if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT_MODE)
+                == HandwritingGesture.GESTURE_TYPE_INSERT_MODE) {
+            list.add(InsertModeGesture.class);
+        }
         if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE)
                 == HandwritingGesture.GESTURE_TYPE_DELETE) {
             list.add(DeleteGesture.class);
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 1f4a7af..e7207fa 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -142,6 +142,13 @@
     public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6;
 
     /**
+     * Gesture of type {@link InsertModeGesture} to begin an insert mode at a designated point.
+     * @hide
+     */
+    @TestApi
+    public static final int GESTURE_TYPE_INSERT_MODE = 1 << 7;
+
+    /**
      * Type of gesture like {@link #GESTURE_TYPE_SELECT}, {@link #GESTURE_TYPE_INSERT},
      * or {@link #GESTURE_TYPE_DELETE}.
      */
@@ -150,6 +157,7 @@
             GESTURE_TYPE_SELECT,
             GESTURE_TYPE_SELECT_RANGE,
             GESTURE_TYPE_INSERT,
+            GESTURE_TYPE_INSERT_MODE,
             GESTURE_TYPE_DELETE,
             GESTURE_TYPE_DELETE_RANGE,
             GESTURE_TYPE_REMOVE_SPACE,
@@ -168,6 +176,7 @@
             GESTURE_TYPE_SELECT,
             GESTURE_TYPE_SELECT_RANGE,
             GESTURE_TYPE_INSERT,
+            GESTURE_TYPE_INSERT_MODE,
             GESTURE_TYPE_DELETE,
             GESTURE_TYPE_DELETE_RANGE,
             GESTURE_TYPE_REMOVE_SPACE,
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index a07dedc..3b6ec80 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,36 +16,24 @@
 
 package android.view.inputmethod;
 
-import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
-import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
-import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
-import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
 import android.util.Log;
-import android.view.InsetsController.AnimationType;
-import android.view.SurfaceControl;
 
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.util.LatencyTracker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
 import java.util.Arrays;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 import java.util.stream.Collectors;
@@ -397,35 +385,15 @@
     void onHidden(@Nullable Token token);
 
     /**
-     * Get the singleton request tracker instance.
+     * Get the singleton instance of this class.
      *
-     * @return the singleton request tracker instance
+     * @return the singleton instance of this class
      */
     @NonNull
-    static ImeTracker forLogging() {
+    static ImeTracker get() {
         return LOGGER;
     }
 
-    /**
-     * Get the singleton jank tracker instance.
-     *
-     * @return the singleton jank tracker instance
-     */
-    @NonNull
-    static ImeJankTracker forJank() {
-        return JANK_TRACKER;
-    }
-
-    /**
-     * Get the singleton latency tracker instance.
-     *
-     * @return the singleton latency tracker instance
-     */
-    @NonNull
-    static ImeLatencyTracker forLatency() {
-        return LATENCY_TRACKER;
-    }
-
     /** The singleton IME tracker instance. */
     @NonNull
     ImeTracker LOGGER = new ImeTracker() {
@@ -521,12 +489,6 @@
         }
     };
 
-    /** The singleton IME tracker instance for instrumenting jank metrics. */
-    ImeJankTracker JANK_TRACKER = new ImeJankTracker();
-
-    /** The singleton IME tracker instance for instrumenting latency metrics. */
-    ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
-
     /** A token that tracks the progress of an IME request. */
     class Token implements Parcelable {
 
@@ -630,153 +592,4 @@
             }
         }
     }
-
-    /**
-     * Context related to {@link InteractionJankMonitor}.
-     */
-    interface InputMethodJankContext {
-        /**
-         * @return a context associated with a display
-         */
-        Context getDisplayContext();
-
-        /**
-         * @return a SurfaceControl is going to be monitored
-         */
-        SurfaceControl getTargetSurfaceControl();
-
-        /**
-         * @return the package name of the host
-         */
-        String getHostPackageName();
-    }
-
-    /**
-     * Context related to {@link LatencyTracker}.
-     */
-    interface InputMethodLatencyContext {
-        /**
-         * @return a context associated with current application
-         */
-        Context getAppContext();
-    }
-
-    /**
-     * A tracker instance which is in charge of communicating with {@link InteractionJankMonitor}.
-     */
-    final class ImeJankTracker {
-
-        private ImeJankTracker() {
-        }
-
-        /**
-         * Called when the animation, which is going to be monitored, starts.
-         *
-         * @param jankContext context which is needed by {@link InteractionJankMonitor}
-         * @param animType {@link AnimationType}
-         * @param useSeparatedThread {@code true} if the animation is handled by the app,
-         *                           {@code false} if the animation will be scheduled on the
-         *                           {@link android.view.InsetsAnimationThread}
-         */
-        public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
-                @AnimationType int animType, boolean useSeparatedThread) {
-            if (jankContext.getDisplayContext() == null
-                    || jankContext.getTargetSurfaceControl() == null) {
-                return;
-            }
-            final Configuration.Builder builder = Configuration.Builder.withSurface(
-                            CUJ_IME_INSETS_ANIMATION,
-                            jankContext.getDisplayContext(),
-                            jankContext.getTargetSurfaceControl())
-                    .setTag(String.format(Locale.US, "%d@%d@%s", animType,
-                            useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
-            InteractionJankMonitor.getInstance().begin(builder);
-        }
-
-        /**
-         * Called when the animation, which is going to be monitored, cancels.
-         */
-        public void onCancelAnimation() {
-            InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
-        }
-
-        /**
-         * Called when the animation, which is going to be monitored, ends.
-         */
-        public void onFinishAnimation() {
-            InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
-        }
-    }
-
-    /**
-     * A tracker instance which is in charge of communicating with {@link LatencyTracker}.
-     */
-    final class ImeLatencyTracker {
-
-        private ImeLatencyTracker() {
-        }
-
-        private boolean shouldMonitorLatency(@SoftInputShowHideReason int reason) {
-            return reason == SoftInputShowHideReason.SHOW_SOFT_INPUT
-                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT
-                    || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API
-                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API
-                    || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME
-                    || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME;
-        }
-
-        public void onRequestShow(@Nullable Token token, @Origin int origin,
-                @SoftInputShowHideReason int reason,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            if (!shouldMonitorLatency(reason)) return;
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionStart(
-                            ACTION_REQUEST_IME_SHOWN,
-                            softInputDisplayReasonToString(reason));
-        }
-
-        public void onRequestHide(@Nullable Token token, @Origin int origin,
-                @SoftInputShowHideReason int reason,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            if (!shouldMonitorLatency(reason)) return;
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionStart(
-                            ACTION_REQUEST_IME_HIDDEN,
-                            softInputDisplayReasonToString(reason));
-        }
-
-        public void onShowFailed(@Nullable Token token, @Phase int phase,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            onShowCancelled(token, phase, latencyContext);
-        }
-
-        public void onHideFailed(@Nullable Token token, @Phase int phase,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            onHideCancelled(token, phase, latencyContext);
-        }
-
-        public void onShowCancelled(@Nullable Token token, @Phase int phase,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionCancel(ACTION_REQUEST_IME_SHOWN);
-        }
-
-        public void onHideCancelled(@Nullable Token token, @Phase int phase,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionCancel(ACTION_REQUEST_IME_HIDDEN);
-        }
-
-        public void onShown(@Nullable Token token,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionEnd(ACTION_REQUEST_IME_SHOWN);
-        }
-
-        public void onHidden(@Nullable Token token,
-                @NonNull InputMethodLatencyContext latencyContext) {
-            LatencyTracker.getInstance(latencyContext.getAppContext())
-                    .onActionEnd(ACTION_REQUEST_IME_HIDDEN);
-        }
-    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 642182b..99bd02d 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2045,11 +2045,10 @@
     private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         if (statsToken == null) {
-            statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
         }
-        ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                reason, ActivityThread::currentApplication);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
         // Re-dispatch if there is a context mismatch.
@@ -2061,15 +2060,12 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onShowFailed(
-                        statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
-                        ActivityThread::currentApplication);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                 return false;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2099,20 +2095,20 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
-                    null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT);
 
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
                     + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
                     + " please update to version 26.0 or newer version.");
             if (mCurRootView == null || mCurRootView.getView() == null) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
                 return;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2190,24 +2186,20 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
-                null /* component */, Process.myUid(),
-                ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason, ActivityThread::currentApplication);
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onHideFailed(statsToken,
-                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 return false;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                     flags, resultReceiver, reason);
@@ -2843,22 +2835,18 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
-        final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
-                null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_SOFT_INPUT, ActivityThread::currentApplication);
 
         synchronized (mH) {
             if (mCurRootView == null || mCurRootView.getView() == null) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onHideFailed(statsToken,
-                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             IInputMethodManagerGlobalInvoker.hideSoftInput(
                     mClient,
@@ -2917,13 +2905,11 @@
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
-                ImeTracker.forLogging().onFailed(statsToken,
-                        ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
                 return false;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken,
-                    ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
 
             showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
@@ -2941,25 +2927,21 @@
      */
     public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
         if (statsToken == null) {
-            statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
-        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
-                ActivityThread::currentApplication);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
         synchronized (mH) {
             if (!isImeSessionAvailableLocked() || mCurRootView == null
                     || mCurRootView.getWindowToken() != windowToken) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
-                ImeTracker.forLatency().onHideFailed(statsToken,
-                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 return;
             }
 
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                     0 /* flags */, null /* resultReceiver */,
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.aidl b/core/java/android/view/inputmethod/InsertModeGesture.aidl
new file mode 100644
index 0000000..a0790b8
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable InsertModeGesture;
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.java b/core/java/android/view/inputmethod/InsertModeGesture.java
new file mode 100644
index 0000000..6b9d7fb
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.os.CancellationSignal;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for starting an insert mode which inserts a space in
+ * the editor to let users hand write freely at the designated insertion point.
+ * This class holds the information required for insertion of text in
+ * toolkit widgets like {@link TextView}.
+ *
+ * Once InsertMode gesture is started, it continues until IME calls
+ * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+ * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)} obtained from
+ * {@link #getCancellationSignal()}.
+ */
+public final class InsertModeGesture extends HandwritingGesture implements Parcelable {
+
+    private PointF mPoint;
+    private CancellationSignal mCancellationSignal;
+
+    private InsertModeGesture(PointF point, String fallbackText,
+            CancellationSignal cancellationSignal) {
+        mType = GESTURE_TYPE_INSERT_MODE;
+        mPoint = point;
+        mFallbackText = fallbackText;
+        mCancellationSignal = cancellationSignal;
+    }
+
+    private InsertModeGesture(final Parcel source) {
+        mType = GESTURE_TYPE_INSERT_MODE;
+        mFallbackText = source.readString8();
+        mPoint = source.readTypedObject(PointF.CREATOR);
+    }
+
+    /**
+     * Returns the {@link CancellationSignal} associated with finishing this gesture.
+     * Once InsertMode gesture is started, it continues until IME calls
+     * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+     * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)}.
+     */
+    @NonNull
+    public CancellationSignal getCancellationSignal() {
+        return mCancellationSignal;
+    }
+
+    /**
+     * Returns the insertion point {@link PointF} (in screen coordinates) where space will be
+     * created for additional text to be inserted.
+     */
+    @NonNull
+    public PointF getInsertionPoint() {
+        return mPoint;
+    }
+
+    /**
+     * Builder for {@link InsertModeGesture}. This class is not designed to be thread-safe.
+     */
+    public static final class Builder {
+        private PointF mPoint;
+        private String mFallbackText;
+        // TODO(b/254727073): implement CancellationSignal
+        private CancellationSignal mCancellationSignal;
+
+        /**
+         * Sets the insertion point (in screen coordinates) where space will be created for
+         * additional text to be inserted.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setInsertionPoint(@NonNull PointF point) {
+            mPoint = point;
+            return this;
+        }
+
+        /**
+         * Sets the {@link CancellationSignal} used to cancel the ongoing gesture.
+         * @param cancellationSignal signal to cancel an ongoing gesture.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setCancellationSignal(@NonNull CancellationSignal cancellationSignal) {
+            mCancellationSignal = cancellationSignal;
+            return this;
+        }
+
+        /**
+         * Set fallback text that will be committed at current cursor position if there is no
+         * applicable text beneath the area of gesture.
+         * @param fallbackText text to set
+         */
+        @NonNull
+        public Builder setFallbackText(@Nullable String fallbackText) {
+            mFallbackText = fallbackText;
+            return this;
+        }
+
+        /**
+         * Returns {@link InsertModeGesture} using parameters in this
+         * {@link InsertModeGesture.Builder}.
+         * @throws IllegalArgumentException if one or more positional parameters are not specified.
+         */
+        @NonNull
+        public InsertModeGesture build() {
+            if (mPoint == null) {
+                throw new IllegalArgumentException("Insertion point must be set.");
+            } else if (mCancellationSignal == null) {
+                throw new IllegalArgumentException("CancellationSignal must be set.");
+            }
+            return new InsertModeGesture(mPoint, mFallbackText, mCancellationSignal);
+        }
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @NonNull
+    public static final Creator<InsertModeGesture> CREATOR = new Creator<>() {
+        @Override
+        public InsertModeGesture createFromParcel(Parcel source) {
+            return new InsertModeGesture(source);
+        }
+
+        @Override
+        public InsertModeGesture[] newArray(int size) {
+            return new InsertModeGesture[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPoint, mFallbackText);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof InsertModeGesture)) return false;
+
+        InsertModeGesture that = (InsertModeGesture) o;
+
+        if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+        return Objects.equals(mPoint, that.mPoint);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mFallbackText);
+        dest.writeTypedObject(mPoint, flags);
+    }
+}
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
index e4066fc..ae7df0f 100644
--- a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
@@ -70,6 +70,8 @@
                 return SelectRangeGesture.CREATOR.createFromParcel(parcel);
             case HandwritingGesture.GESTURE_TYPE_INSERT:
                 return InsertGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_INSERT_MODE:
+                return InsertModeGesture.CREATOR.createFromParcel(parcel);
             case HandwritingGesture.GESTURE_TYPE_DELETE:
                 return DeleteGesture.CREATOR.createFromParcel(parcel);
             case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 2316738..5e2eceb 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -16,8 +16,8 @@
 package com.android.internal.app;
 
 import android.annotation.IntDef;
-import android.annotation.Nullable;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.content.ContentResolver;
@@ -60,16 +60,19 @@
     private Set<Integer> mLoadedPages;
     private final EmptyStateProvider mEmptyStateProvider;
     private final UserHandle mWorkProfileUserHandle;
+    private final UserHandle mCloneUserHandle;
     private final QuietModeManager mQuietModeManager;
 
     AbstractMultiProfilePagerAdapter(Context context, int currentPage,
             EmptyStateProvider emptyStateProvider,
             QuietModeManager quietModeManager,
-            UserHandle workProfileUserHandle) {
+            UserHandle workProfileUserHandle,
+            UserHandle cloneUserHandle) {
         mContext = Objects.requireNonNull(context);
         mCurrentPage = currentPage;
         mLoadedPages = new HashSet<>();
         mWorkProfileUserHandle = workProfileUserHandle;
+        mCloneUserHandle = cloneUserHandle;
         mEmptyStateProvider = emptyStateProvider;
         mQuietModeManager = quietModeManager;
     }
@@ -160,6 +163,10 @@
         return null;
     }
 
+    public UserHandle getCloneUserHandle() {
+        return mCloneUserHandle;
+    }
+
     /**
      * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
      * <ul>
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 9759540..930f6e0 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -30,11 +30,16 @@
 import android.util.Log;
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
 
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Used to sort resolved activities in {@link ResolverListController}.
@@ -48,8 +53,8 @@
     private static final String TAG = "AbstractResolverComp";
 
     protected AfterCompute mAfterCompute;
-    protected final PackageManager mPm;
-    protected final UsageStatsManager mUsm;
+    protected final Map<UserHandle, PackageManager> mPmMap = new HashMap<>();
+    protected final Map<UserHandle, UsageStatsManager> mUsmMap = new HashMap<>();
     protected String[] mAnnotations;
     protected String mContentType;
 
@@ -98,14 +103,28 @@
         }
     };
 
-    public AbstractResolverComparator(Context context, Intent intent) {
+    // context here refers to the activity calling this comparator.
+    // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+    public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+            UserHandle targetUserSpace) {
+        this(launchedFromContext, intent, Lists.newArrayList(targetUserSpace));
+    }
+
+    // context here refers to the activity calling this comparator.
+    // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+    public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+            List<UserHandle> targetUserSpaceList) {
         String scheme = intent.getScheme();
         mHttp = "http".equals(scheme) || "https".equals(scheme);
         mContentType = intent.getType();
         getContentAnnotations(intent);
-        mPm = context.getPackageManager();
-        mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
-        mAzComparator = new AzInfoComparator(context);
+        for (UserHandle user : targetUserSpaceList) {
+            Context userContext = launchedFromContext.createContextAsUser(user, 0);
+            mPmMap.put(user, userContext.getPackageManager());
+            mUsmMap.put(user,
+                    (UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE));
+        }
+        mAzComparator = new AzInfoComparator(launchedFromContext);
     }
 
     // get annotations of content from intent.
@@ -208,8 +227,8 @@
 
     /**
      * Computes features for each target. This will be called before calls to {@link
-     * #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
-     * comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
+     * #getScore(TargetInfo)} or {@link #compare(ResolveInfo, ResolveInfo)}, in order to prepare the
+     * comparator for those calls. Note that {@link #getScore(TargetInfo)} uses {@link
      * ComponentName}, so the implementation will have to be prepared to identify a {@link
      * ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
      * before doing any computing.
@@ -226,7 +245,7 @@
      * Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
      * when {@link #compute(List)} was called before this.
      */
-    abstract float getScore(ComponentName name);
+    abstract float getScore(TargetInfo targetInfo);
 
     /** Handles result message sent to mHandler. */
     abstract void handleResultMessage(Message message);
@@ -234,9 +253,11 @@
     /**
      * Reports to UsageStats what was chosen.
      */
-    final void updateChooserCounts(String packageName, int userId, String action) {
-        if (mUsm != null) {
-            mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+    final void updateChooserCounts(String packageName, UserHandle user, String action) {
+        if (mUsmMap.containsKey(user)) {
+            mUsmMap.get(user)
+                    .reportChooserSelection(packageName, user.getIdentifier(), mContentType,
+                            mAnnotations, action);
         }
     }
 
@@ -246,9 +267,9 @@
      * <p>Default implementation does nothing, as we could have simple model that does not train
      * online.
      *
-     * @param componentName the component that the user clicked
+     * @param targetInfo the target that the user clicked.
      */
-    void updateModel(ComponentName componentName) {
+    void updateModel(TargetInfo targetInfo) {
     }
 
     /** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 115a9d7..b9f0236 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -31,6 +31,9 @@
 import android.util.Log;
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -70,7 +73,7 @@
             AppPredictor appPredictor,
             UserHandle user,
             ChooserActivityLogger chooserActivityLogger) {
-        super(context, intent);
+        super(context, intent, Lists.newArrayList(user));
         mContext = context;
         mIntent = intent;
         mAppPredictor = appPredictor;
@@ -99,13 +102,13 @@
     }
 
     @Override
-    float getScore(ComponentName name) {
-        return mComparatorModel.getScore(name);
+    float getScore(TargetInfo targetInfo) {
+        return mComparatorModel.getScore(targetInfo);
     }
 
     @Override
-    void updateModel(ComponentName componentName) {
-        mComparatorModel.notifyOnTargetSelected(componentName);
+    void updateModel(TargetInfo targetInfo) {
+        mComparatorModel.notifyOnTargetSelected(targetInfo);
     }
 
     @Override
@@ -158,9 +161,12 @@
     private void setupFallbackModel(List<ResolvedComponentInfo> targets) {
         mResolverRankerService =
                 new ResolverRankerServiceResolverComparator(
-                        mContext, mIntent, mReferrerPackage,
+                        mContext,
+                        mIntent,
+                        mReferrerPackage,
                         () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
-                        getChooserActivityLogger());
+                        getChooserActivityLogger(),
+                        mUser);
         mComparatorModel = mModelBuilder.buildFallbackModel(mResolverRankerService);
         mResolverRankerService.compute(targets);
     }
@@ -224,13 +230,13 @@
                     }
 
                     @Override
-                    public float getScore(ComponentName componentName) {
-                        return comparator.getScore(componentName);
+                    public float getScore(TargetInfo targetInfo) {
+                        return comparator.getScore(targetInfo);
                     }
 
                     @Override
-                    public void notifyOnTargetSelected(ComponentName componentName) {
-                        comparator.updateModel(componentName);
+                    public void notifyOnTargetSelected(TargetInfo targetInfo) {
+                        comparator.updateModel(targetInfo);
                     }
                 };
         }
@@ -271,8 +277,8 @@
         }
 
         @Override
-        public float getScore(ComponentName name) {
-            Integer rank = mTargetRanks.get(name);
+        public float getScore(TargetInfo targetInfo) {
+            Integer rank = mTargetRanks.get(targetInfo.getResolvedComponentName());
             if (rank == null) {
                 Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
                 return 0f;
@@ -282,13 +288,14 @@
         }
 
         @Override
-        public void notifyOnTargetSelected(ComponentName componentName) {
+        public void notifyOnTargetSelected(TargetInfo targetInfo) {
             mAppPredictor.notifyAppTargetEvent(
                     new AppTargetEvent.Builder(
                         new AppTarget.Builder(
-                            new AppTargetId(componentName.toString()),
-                            componentName.getPackageName(), mUser)
-                            .setClassName(componentName.getClassName()).build(),
+                            new AppTargetId(targetInfo.getResolvedComponentName().toString()),
+                                targetInfo.getResolvedComponentName().getPackageName(), mUser)
+                            .setClassName(targetInfo.getResolvedComponentName()
+                                    .getClassName()).build(),
                         ACTION_LAUNCH).build());
         }
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 9f283d4..f257f1c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -873,7 +873,7 @@
 
         return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
                 noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
-                createCrossProfileIntentsChecker(), createMyUserIdProvider());
+                createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch());
     }
 
     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
@@ -886,13 +886,14 @@
                 initialIntents,
                 rList,
                 filterLastUsed,
-                /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+                /* userHandle */ getPersonalProfileUserHandle());
         return new ChooserMultiProfilePagerAdapter(
                 /* context */ this,
                 adapter,
                 createEmptyStateProvider(/* workProfileUserHandle= */ null),
                 mQuietModeManager,
                 /* workProfileUserHandle= */ null,
+                getCloneProfileUserHandle(),
                 mMaxTargetsPerRow);
     }
 
@@ -923,13 +924,14 @@
                 mQuietModeManager,
                 selectedProfile,
                 getWorkProfileUserHandle(),
+                getCloneProfileUserHandle(),
                 mMaxTargetsPerRow);
     }
 
     private int findSelectedProfile() {
         int selectedProfile = getSelectedProfileExtra();
         if (selectedProfile == -1) {
-            selectedProfile = getProfileForUser(getUser());
+            selectedProfile = getProfileForUser(getTabOwnerUserHandleForLaunch());
         }
         return selectedProfile;
     }
@@ -1800,8 +1802,12 @@
             targetList = new ArrayList<DisplayResolveInfo>();
             targetList.add((DisplayResolveInfo) targetInfo);
         }
+        // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
+        // resolved correctly.
         bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
-                mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+                getResolveInfoUserHandle(
+                        targetInfo.getResolveInfo(),
+                        mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
         bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
                 targetList);
         fragment.setArguments(bundle);
@@ -1865,8 +1871,11 @@
             if (!mti.hasSelected()) {
                 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
                 Bundle b = new Bundle();
+                // Add userHandle based badge to the stackedAppDialogBox.
                 b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
-                        mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+                        getResolveInfoUserHandle(
+                                targetInfo.getResolveInfo(),
+                                mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
                 b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
                         mti);
                 b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
@@ -2259,9 +2268,11 @@
                         mChooserMultiProfilePagerAdapter.getActiveListAdapter();
                 if (currentListAdapter != null) {
                     sendImpressionToAppPredictor(info, currentListAdapter);
-                    currentListAdapter.updateModel(info.getResolvedComponentName());
-                    currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
-                            targetIntent.getAction());
+                    currentListAdapter.updateModel(info);
+                    currentListAdapter.updateChooserCounts(
+                            ri.activityInfo.packageName,
+                            targetIntent.getAction(),
+                            ri.userHandle);
                 }
                 if (DEBUG) {
                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
@@ -2386,7 +2397,10 @@
      */
     @Nullable
     private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
-        return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
+        // We cannot use APS service when clone profile is present as APS service cannot sort
+        // cross profile targets as of now.
+        return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES && getCloneProfileUserHandle() == null
+                ? createAppPredictor(userHandle) : null;
     }
 
     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
@@ -2433,15 +2447,24 @@
      * Sort intents alphabetically based on display label.
      */
     static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
-        Collator mCollator;
+        Comparator<DisplayResolveInfo> mComparator;
         AzInfoComparator(Context context) {
-            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+            Collator collator = Collator
+                    .getInstance(context.getResources().getConfiguration().locale);
+            // Adding two stage comparator, first stage compares using displayLabel, next stage
+            //  compares using resolveInfo.userHandle
+            mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
+                    .thenComparingInt(displayResolveInfo ->
+                            getResolveInfoUserHandle(
+                                    displayResolveInfo.getResolveInfo(),
+                                    // TODO: User resolveInfo.userHandle, once its available.
+                                    UserHandle.SYSTEM).getIdentifier());
         }
 
         @Override
         public int compare(
                 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
-            return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
+            return mComparator.compare(lhsp, rhsp);
         }
     }
 
@@ -2466,9 +2489,10 @@
                 String referrerPackageName,
                 int launchedFromUid,
                 UserHandle userId,
-                AbstractResolverComparator resolverComparator) {
+                AbstractResolverComparator resolverComparator,
+                UserHandle queryIntentsAsUser) {
             super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
-                    resolverComparator);
+                    resolverComparator, queryIntentsAsUser);
         }
 
         @Override
@@ -2529,10 +2553,16 @@
                     getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
         } else {
             resolverComparator =
-                    new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
-                        getReferrerPackageName(), null, getChooserActivityLogger());
+                    new ResolverRankerServiceResolverComparator(
+                            this,
+                            getTargetIntent(),
+                            getReferrerPackageName(),
+                            null,
+                            getChooserActivityLogger(),
+                            getResolverRankerServiceUserHandleList(userHandle));
         }
 
+        UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
         return new ChooserListController(
                 this,
                 mPm,
@@ -2540,7 +2570,8 @@
                 getReferrerPackageName(),
                 mLaunchedFromUid,
                 userHandle,
-                resolverComparator);
+                resolverComparator,
+                queryIntentsUser == null ? userHandle : queryIntentsUser);
     }
 
     @VisibleForTesting
@@ -2741,17 +2772,16 @@
     }
 
     /**
-     * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
-     * does not match either the personal or work user handle.
+     * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
+     * Returns {@link #PROFILE_PERSONAL}, otherwise.
      **/
     private int getProfileForUser(UserHandle currentUserHandle) {
-        if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
-            return PROFILE_PERSONAL;
-        } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
+        if (currentUserHandle.equals(getWorkProfileUserHandle())) {
             return PROFILE_WORK;
         }
-        Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
-        return -1;
+        // We return personal profile, as it is the default when there is no work profile, personal
+        // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
+        return PROFILE_PERSONAL;
     }
 
     private ViewGroup getActiveEmptyStateView() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 2ae2c09..e0568cf 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -197,6 +197,7 @@
                     ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                     ri.icon = li.getIconResource();
                     ri.iconResourceId = ri.icon;
+                    ri.userHandle = getUserHandle();
                 }
                 if (userManager.isManagedProfile()) {
                     ri.noResourceId = true;
@@ -351,7 +352,9 @@
                 Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
                 for (DisplayResolveInfo info : allTargets) {
                     String resolvedTarget = info.getResolvedComponentName().getPackageName()
-                            + '#' + info.getDisplayLabel();
+                            + '#' + info.getDisplayLabel()
+                            + '#' + ResolverActivity.getResolveInfoUserHandle(
+                                    info.getResolveInfo(), getUserHandle()).getIdentifier();
                     DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
                     if (multiDri == null) {
                         consolidated.put(resolvedTarget, info);
@@ -367,7 +370,8 @@
                 }
                 List<DisplayResolveInfo> groupedTargets = new ArrayList<>();
                 groupedTargets.addAll(consolidated.values());
-                Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext));
+                Collections.sort(groupedTargets,
+                        new ChooserActivity.AzInfoComparator(mContext));
                 return groupedTargets;
             }
             @Override
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 0509b67..f56f818 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -45,9 +45,10 @@
             EmptyStateProvider emptyStateProvider,
             QuietModeManager quietModeManager,
             UserHandle workProfileUserHandle,
+            UserHandle cloneUserHandle,
             int maxTargetsPerRow) {
         super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
-                workProfileUserHandle);
+                workProfileUserHandle, cloneUserHandle);
         mItems = new ChooserProfileDescriptor[] {
                 createProfileDescriptor(adapter)
         };
@@ -61,9 +62,10 @@
             QuietModeManager quietModeManager,
             @Profile int defaultProfile,
             UserHandle workProfileUserHandle,
+            UserHandle cloneUserHandle,
             int maxTargetsPerRow) {
         super(context, /* currentPage */ defaultProfile, emptyStateProvider,
-                quietModeManager, workProfileUserHandle);
+                quietModeManager, workProfileUserHandle, cloneUserHandle);
         mItems = new ChooserProfileDescriptor[] {
                 createProfileDescriptor(personalAdapter),
                 createProfileDescriptor(workAdapter)
@@ -110,11 +112,12 @@
     @Override
     @Nullable
     ChooserListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
-        if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
-            return getActiveListAdapter();
-        } else if (getInactiveListAdapter() != null
-                && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
-            return getInactiveListAdapter();
+        if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+                || userHandle.equals(getCloneUserHandle())) {
+            return getPersonalListAdapter();
+        } else if (getWorkListAdapter() != null
+                && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+            return getWorkListAdapter();
         }
         return null;
     }
@@ -153,13 +156,13 @@
     }
 
     @Override
-    public ResolverListAdapter getPersonalListAdapter() {
+    public ChooserListAdapter getPersonalListAdapter() {
         return getAdapterForIndex(PROFILE_PERSONAL).getListAdapter();
     }
 
     @Override
     @Nullable
-    public ResolverListAdapter getWorkListAdapter() {
+    public ChooserListAdapter getWorkListAdapter() {
         return getAdapterForIndex(PROFILE_WORK).getListAdapter();
     }
 
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3efd279..3619c7b 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -21,6 +21,7 @@
 import android.app.ListFragment;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.LocaleList;
 import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -102,15 +103,21 @@
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
             LocaleSelectedListener listener, boolean translatedOnly) {
-        return createLanguagePicker(context, listener, translatedOnly, null, null);
+        return createLanguagePicker(context, listener, translatedOnly, null, null, null);
     }
 
     public static LocalePickerWithRegion createLanguagePicker(Context context,
-            LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
-            OnActionExpandListener onActionExpandListener) {
+            LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales) {
+        return createLanguagePicker(context, listener, translatedOnly, explicitLocales, null, null);
+    }
+
+    /** Creates language picker UI */
+    public static LocalePickerWithRegion createLanguagePicker(Context context,
+            LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales,
+            String appPackageName, OnActionExpandListener onActionExpandListener) {
         LocaleCollectorBase localePickerController;
         if (TextUtils.isEmpty(appPackageName)) {
-            localePickerController = new SystemLocaleCollector(context);
+            localePickerController = new SystemLocaleCollector(context, explicitLocales);
         } else {
             localePickerController = new AppLocaleCollector(context, appPackageName);
         }
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index d2eee91..bcff907 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -22,6 +22,7 @@
 import android.os.LocaleList;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -29,6 +30,7 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IllformedLocaleException;
@@ -106,6 +108,9 @@
             return mParent;
         }
 
+        /**
+         * TODO: This method may rename to be more generic i.e. toLanguageTag().
+         */
         @UnsupportedAppUsage
         public String getId() {
             return mId;
@@ -456,11 +461,30 @@
     @UnsupportedAppUsage
     public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
             LocaleInfo parent, boolean translatedOnly) {
+        return getLevelLocales(context, ignorables, parent, translatedOnly, null);
+    }
+
+    /**
+     * @param explicitLocales Indicates only the locales within this list should be shown in the
+     *                       locale picker.
+     *
+     * Returns a list of locales for language or region selection.
+     * If the parent is null, then it is the language list.
+     * If it is not null, then the list will contain all the locales that belong to that parent.
+     * Example: if the parent is "ar", then the region list will contain all Arabic locales.
+     * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+     */
+    public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
+            LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
         fillCache(context);
         String parentId = parent == null ? null : parent.getId();
-
         HashSet<LocaleInfo> result = new HashSet<>();
-        for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
+        HashMap<String, LocaleInfo> supportedLcoaleInfos =
+                explicitLocales == null
+                        ? sLocaleCache
+                        : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+
+        for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
             int level = getLevel(ignorables, li, translatedOnly);
             if (level == 2) {
                 if (parent != null) { // region selection
@@ -479,6 +503,61 @@
         return result;
     }
 
+    /** Converts string array of explicit locales to HashMap */
+    public static HashMap<String, LocaleInfo> convertExplicitLocales(
+            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+        // Trys to find the matched locale within android supported locales. If there is no matched
+        // locale, it will still keep the unsupported lcoale in list.
+        // Note: This currently does not support unicode extension check.
+        LocaleList localeList = matchLocaleFromSupportedLocaleList(
+                explicitLocales, localeinfo);
+
+        HashMap<String, LocaleInfo> localeInfos = new HashMap<>();
+        for (int i = 0; i < localeList.size(); i++) {
+            Locale locale = localeList.get(i);
+            if (locale.toString().isEmpty()) {
+                throw new IllformedLocaleException("Bad locale entry");
+            }
+
+            LocaleInfo li = new LocaleInfo(locale);
+            if (localeInfos.containsKey(li.getId())) {
+                continue;
+            }
+            localeInfos.put(li.getId(), li);
+            Locale parent = li.getParent();
+            if (parent != null) {
+                String parentId = parent.toLanguageTag();
+                if (!localeInfos.containsKey(parentId)) {
+                    localeInfos.put(parentId, new LocaleInfo(parent));
+                }
+            }
+        }
+        return localeInfos;
+    }
+
+    private static LocaleList matchLocaleFromSupportedLocaleList(
+            LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+        //TODO: Adds a function for unicode extension if needed.
+        Locale[] resultLocales = new Locale[explicitLocales.size()];
+        for (int i = 0; i < explicitLocales.size(); i++) {
+            Locale locale = explicitLocales.get(i).stripExtensions();
+            if (!TextUtils.isEmpty(locale.getCountry())) {
+                for (LocaleInfo localeInfo :localeinfo) {
+                    if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
+                            && TextUtils.equals(locale.getCountry(),
+                            localeInfo.getLocale().getCountry())) {
+                        resultLocales[i] = localeInfo.getLocale();
+                        continue;
+                    }
+                }
+            }
+            if (resultLocales[i] == null) {
+                resultLocales[i] = locale;
+            }
+        }
+        return new LocaleList(resultLocales);
+    }
+
     @UnsupportedAppUsage
     public static LocaleInfo getLocaleInfo(Locale locale) {
         String id = locale.toLanguageTag();
diff --git a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
index 34249f2..747780b 100644
--- a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
@@ -28,10 +28,9 @@
 import android.os.UserHandle;
 import android.stats.devicepolicy.nano.DevicePolicyEnums;
 
+import com.android.internal.R;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
-import com.android.internal.R;
 
 import java.util.List;
 
@@ -50,16 +49,16 @@
     @NonNull
     private final String mMetricsCategory;
     @NonNull
-    private final MyUserIdProvider mMyUserIdProvider;
+    private final UserHandle mTabOwnerUserHandleForLaunch;
 
     public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
             UserHandle personalProfileUserHandle, String metricsCategory,
-            MyUserIdProvider myUserIdProvider) {
+            UserHandle tabOwnerUserHandleForLaunch) {
         mContext = context;
         mWorkProfileUserHandle = workProfileUserHandle;
         mPersonalProfileUserHandle = personalProfileUserHandle;
         mMetricsCategory = metricsCategory;
-        mMyUserIdProvider = myUserIdProvider;
+        mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
     }
 
     @Nullable
@@ -69,7 +68,7 @@
         UserHandle listUserHandle = resolverListAdapter.getUserHandle();
 
         if (mWorkProfileUserHandle != null
-                && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+                && (mTabOwnerUserHandleForLaunch.equals(listUserHandle)
                 || !hasAppsInOtherProfile(resolverListAdapter))) {
 
             String title;
@@ -102,7 +101,7 @@
             return false;
         }
         List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
-                adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+                adapter.getResolversForUser(mTabOwnerUserHandleForLaunch);
         for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
             ResolveInfo resolveInfo = info.getResolveInfoAt(0);
             if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
diff --git a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
index 2e7d5bf..2046bfc 100644
--- a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
@@ -27,7 +27,6 @@
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
 
 /**
  * Empty state provider that does not allow cross profile sharing, it will return a blocker
@@ -39,28 +38,28 @@
     private final EmptyState mNoWorkToPersonalEmptyState;
     private final EmptyState mNoPersonalToWorkEmptyState;
     private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
-    private final MyUserIdProvider mUserIdProvider;
+    private final UserHandle mTabOwnerUserHandleForLaunch;
 
     public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
             EmptyState noWorkToPersonalEmptyState,
             EmptyState noPersonalToWorkEmptyState,
             CrossProfileIntentsChecker crossProfileIntentsChecker,
-            MyUserIdProvider myUserIdProvider) {
+            UserHandle preselectedTabOwnerUserHandle) {
         mPersonalProfileUserHandle = personalUserHandle;
         mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
         mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
         mCrossProfileIntentsChecker = crossProfileIntentsChecker;
-        mUserIdProvider = myUserIdProvider;
+        mTabOwnerUserHandleForLaunch = preselectedTabOwnerUserHandle;
     }
 
     @Nullable
     @Override
     public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
         boolean shouldShowBlocker =
-                mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+                !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle())
                 && !mCrossProfileIntentsChecker
                         .hasCrossProfileIntents(resolverListAdapter.getIntents(),
-                                mUserIdProvider.getMyUserId(),
+                                mTabOwnerUserHandleForLaunch.getIdentifier(),
                                 resolverListAdapter.getUserHandle().getIdentifier());
 
         if (!shouldShowBlocker) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f098e2c..992e243 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -166,6 +166,7 @@
     @UnsupportedAppUsage
     protected PackageManager mPm;
     protected int mLaunchedFromUid;
+    private UserHandle mLaunchedFromUserHandle;
 
     private static final String TAG = "ResolverActivity";
     private static final boolean DEBUG = false;
@@ -229,12 +230,15 @@
 
     private BroadcastReceiver mWorkProfileStateReceiver;
     private UserHandle mHeaderCreatorUser;
-
+    private UserHandle mPersonalProfileUserHandle;
     private UserHandle mWorkProfileUserHandle;
 
     @Nullable
     private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
 
+    private UserHandle mCloneProfileUserHandle;
+    private UserHandle mTabOwnerUserHandleForLaunch;
+
     protected final LatencyTracker mLatencyTracker = getLatencyTracker();
 
     private LatencyTracker getLatencyTracker() {
@@ -400,6 +404,7 @@
         setProfileSwitchMessage(intent.getContentUserHint());
 
         mLaunchedFromUid = getLaunchedFromUid();
+        mLaunchedFromUserHandle = UserHandle.getUserHandleForUid(mLaunchedFromUid);
         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
             // Gulp!
             finish();
@@ -416,15 +421,21 @@
         mDefaultTitleResId = defaultTitleRes;
 
         mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+        mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
         mWorkProfileUserHandle = fetchWorkProfileUserProfile();
+        mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+        mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
 
         // The last argument of createResolverListAdapter is whether to do special handling
         // of the last used choice to highlight it in the list.  We need to always
         // turn this off when running under voice interaction, since it results in
         // a more complicated UI that the current voice interaction flow is not able
         // to handle. We also turn it off when the work tab is shown to simplify the UX.
+        // We also turn it off when clonedProfile is present on the device, because we might have
+        // different "last chosen" activities in the different profiles, and PackageManager doesn't
+        // provide any more information to help us select between them.
         boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
-                && !shouldShowTabs();
+                && !shouldShowTabs() && !hasCloneProfile();
         mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
         if (configureContentView()) {
             return;
@@ -566,9 +577,12 @@
                 /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
                 /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
 
-        return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
-                noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
-                createCrossProfileIntentsChecker(), createMyUserIdProvider());
+        return new NoCrossProfileEmptyStateProvider(
+                getPersonalProfileUserHandle(),
+                noWorkToPersonalEmptyState,
+                noPersonalToWorkEmptyState,
+                createCrossProfileIntentsChecker(),
+                getTabOwnerUserHandleForLaunch());
     }
 
     protected EmptyStateProvider createEmptyStateProvider(
@@ -589,7 +603,7 @@
                 workProfileUserHandle,
                 getPersonalProfileUserHandle(),
                 getMetricsCategory(),
-                createMyUserIdProvider()
+                getTabOwnerUserHandleForLaunch()
         );
 
         // Return composite provider, the order matters (the higher, the more priority)
@@ -609,14 +623,15 @@
                 initialIntents,
                 rList,
                 filterLastUsed,
-                /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+                /* userHandle */ getPersonalProfileUserHandle());
         QuietModeManager quietModeManager = createQuietModeManager();
         return new ResolverMultiProfilePagerAdapter(
                 /* context */ this,
                 adapter,
                 createEmptyStateProvider(/* workProfileUserHandle= */ null),
                 quietModeManager,
-                /* workProfileUserHandle= */ null);
+                /* workProfileUserHandle= */ null,
+                getCloneProfileUserHandle());
     }
 
     private UserHandle getIntentUser() {
@@ -634,7 +649,7 @@
         // this happens, we check for it here and set the current profile's tab.
         int selectedProfile = getCurrentProfile();
         UserHandle intentUser = getIntentUser();
-        if (!getUser().equals(intentUser)) {
+        if (!getTabOwnerUserHandleForLaunch().equals(intentUser)) {
             if (getPersonalProfileUserHandle().equals(intentUser)) {
                 selectedProfile = PROFILE_PERSONAL;
             } else if (getWorkProfileUserHandle().equals(intentUser)) {
@@ -674,7 +689,8 @@
                 createEmptyStateProvider(getWorkProfileUserHandle()),
                 quietModeManager,
                 selectedProfile,
-                getWorkProfileUserHandle());
+                getWorkProfileUserHandle(),
+                getCloneProfileUserHandle());
     }
 
     protected int appliedThemeResId() {
@@ -701,20 +717,35 @@
     }
 
     protected @Profile int getCurrentProfile() {
-        return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
+        return (UserHandle.myUserId() == getPersonalProfileUserHandle().getIdentifier()
+                ? PROFILE_PERSONAL : PROFILE_WORK);
     }
 
     protected UserHandle getPersonalProfileUserHandle() {
-        return UserHandle.of(ActivityManager.getCurrentUser());
+        return mPersonalProfileUserHandle;
     }
     protected @Nullable UserHandle getWorkProfileUserHandle() {
         return mWorkProfileUserHandle;
     }
 
+    protected @Nullable UserHandle getCloneProfileUserHandle() {
+        return mCloneProfileUserHandle;
+    }
+
+    protected UserHandle getTabOwnerUserHandleForLaunch() {
+        return mTabOwnerUserHandleForLaunch;
+    }
+
+    protected UserHandle fetchPersonalProfileUserHandle() {
+        mPersonalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+        return mPersonalProfileUserHandle;
+    }
+
     protected @Nullable UserHandle fetchWorkProfileUserProfile() {
         mWorkProfileUserHandle = null;
         UserManager userManager = getSystemService(UserManager.class);
-        for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) {
+        for (final UserInfo userInfo : userManager
+                .getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
             if (userInfo.isManagedProfile()) {
                 mWorkProfileUserHandle = userInfo.getUserHandle();
             }
@@ -722,10 +753,38 @@
         return mWorkProfileUserHandle;
     }
 
+    protected @Nullable UserHandle fetchCloneProfileUserHandle() {
+        mCloneProfileUserHandle = null;
+        UserManager userManager = getSystemService(UserManager.class);
+        for (final UserInfo userInfo :
+                userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+            if (userInfo.isCloneProfile()) {
+                mCloneProfileUserHandle = userInfo.getUserHandle();
+            }
+        }
+        return mCloneProfileUserHandle;
+    }
+
+    private UserHandle fetchTabOwnerUserHandleForLaunch() {
+        if (isLaunchedAsCloneProfile()) {
+            return getPersonalProfileUserHandle();
+        }
+        return mLaunchedFromUserHandle;
+    }
+
     private boolean hasWorkProfile() {
         return getWorkProfileUserHandle() != null;
     }
 
+    private boolean hasCloneProfile() {
+        return getCloneProfileUserHandle() != null;
+    }
+
+    private boolean isLaunchedAsCloneProfile() {
+        return hasCloneProfile()
+                && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
+    }
+
     protected boolean shouldShowTabs() {
         return hasWorkProfile() && ENABLE_TABBED_VIEW;
     }
@@ -1123,6 +1182,14 @@
             mAlwaysButton.setEnabled(false);
             return;
         }
+        // In case of clonedProfile being active, we do not allow the 'Always' option in the
+        // disambiguation dialog of Personal Profile as the package manager cannot distinguish
+        // between cross-profile preferred activities.
+        if (hasCloneProfile() && !mMultiProfilePagerAdapter
+                .getCurrentUserHandle().equals(mWorkProfileUserHandle)) {
+            mAlwaysButton.setEnabled(false);
+            return;
+        }
         boolean enabled = false;
         ResolveInfo ri = null;
         if (hasValidSelection) {
@@ -1431,17 +1498,14 @@
         return true;
     }
 
-    @VisibleForTesting
-    public void safelyStartActivity(TargetInfo cti) {
-        // We're dispatching intents that might be coming from legacy apps, so
-        // don't kill ourselves.
-        StrictMode.disableDeathOnFileUriExposure();
-        try {
-            UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
-            safelyStartActivityInternal(cti, currentUserHandle, null);
-        } finally {
-            StrictMode.enableDeathOnFileUriExposure();
-        }
+    /** Start the activity specified by the {@link TargetInfo}.*/
+    public final void safelyStartActivity(TargetInfo cti) {
+        // In case cloned apps are present, we would want to start those apps in cloned user
+        // space, which will not be same as adaptor's userHandle. resolveInfo.userHandle
+        // identifies the correct user space in such cases.
+        UserHandle activityUserHandle = getResolveInfoUserHandle(
+                cti.getResolveInfo(), mMultiProfilePagerAdapter.getCurrentUserHandle());
+        safelyStartActivityAsUser(cti, activityUserHandle, null);
     }
 
     /**
@@ -1449,11 +1513,12 @@
      * @param cti TargetInfo to be launched.
      * @param user User to launch this activity as.
      */
-    @VisibleForTesting
-    public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+    public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
         safelyStartActivityAsUser(cti, user, null);
     }
 
+    // TODO: Make method public final.
+    @VisibleForTesting
     protected void safelyStartActivityAsUser(
             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
         // We're dispatching intents that might be coming from legacy apps, so
@@ -1466,7 +1531,8 @@
         }
     }
 
-    private void safelyStartActivityInternal(
+    @VisibleForTesting
+    protected void safelyStartActivityInternal(
             TargetInfo cti, UserHandle user, @Nullable Bundle options) {
         // If the target is suspended, the activity will not be successfully launched.
         // Do not unregister from package manager updates in this case
@@ -1550,13 +1616,24 @@
 
     @VisibleForTesting
     protected ResolverListController createListController(UserHandle userHandle) {
+        UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
+        ResolverRankerServiceResolverComparator resolverComparator =
+                new ResolverRankerServiceResolverComparator(
+                        this,
+                        getTargetIntent(),
+                        getReferrerPackageName(),
+                        null,
+                        null,
+                        getResolverRankerServiceUserHandleList(userHandle));
         return new ResolverListController(
                 this,
                 mPm,
                 getTargetIntent(),
                 getReferrerPackageName(),
                 mLaunchedFromUid,
-                userHandle);
+                userHandle,
+                resolverComparator,
+                queryIntentsUser);
     }
 
     /**
@@ -2170,16 +2247,10 @@
     public boolean useLayoutWithDefault() {
         // We only use the default app layout when the profile of the active user has a
         // filtered item. We always show the same default app even in the inactive user profile.
-        boolean currentUserAdapterHasFilteredItem;
-        if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
-                == UserHandle.myUserId()) {
-            currentUserAdapterHasFilteredItem =
-                    mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
-        } else {
-            currentUserAdapterHasFilteredItem =
-                    mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem();
-        }
-        return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem;
+        boolean adapterForCurrentUserHasFilteredItem =
+                mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+                        getTabOwnerUserHandleForLaunch()).hasFilteredItem();
+        return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
     }
 
     /**
@@ -2198,7 +2269,14 @@
         return lhs == null ? rhs == null
                 : lhs.activityInfo == null ? rhs.activityInfo == null
                 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
-                && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
+                && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName)
+                        // Comparing against resolveInfo.userHandle in case cloned apps are present,
+                        // as they will have the same activityInfo.
+                && Objects.equals(
+                        getResolveInfoUserHandle(lhs,
+                                mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()),
+                        getResolveInfoUserHandle(rhs,
+                                mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()));
     }
 
     protected String getMetricsCategory() {
@@ -2439,4 +2517,47 @@
     }
 
     protected void maybeLogProfileChange() {}
+
+    /**
+     * Returns the {@link UserHandle} to use when querying resolutions for intents in a
+     * {@link ResolverListController} configured for the provided {@code userHandle}.
+     */
+    protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
+        // In case launching app is in clonedProfile, and we are building the personal tab, intent
+        // resolution will be attempted as clonedUser instead of user 0. This is because intent
+        // resolution from user 0 and clonedUser is not guaranteed to return same results.
+        // We do not care about the case when personal adapter is started with non-root user
+        // (secondary user case), as clone profile is guaranteed to be non-active in that case.
+        UserHandle queryIntentsUser = userHandle;
+        if (isLaunchedAsCloneProfile() && userHandle.equals(getPersonalProfileUserHandle())) {
+            queryIntentsUser = getCloneProfileUserHandle();
+        }
+        return queryIntentsUser;
+    }
+
+    /**
+     * This function is temporary in nature, and its usages will be replaced with just
+     * resolveInfo.userHandle, once it is available, once sharesheet is stable.
+     */
+    public static UserHandle getResolveInfoUserHandle(ResolveInfo resolveInfo,
+            UserHandle predictedHandle) {
+        return resolveInfo.userHandle;
+    }
+
+    /**
+     * Returns the {@link List} of {@link UserHandle} to pass on to the
+     * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
+     */
+    protected final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
+        List<UserHandle> userList = new ArrayList<>();
+        userList.add(userHandle);
+        // Add clonedProfileUserHandle to the list only if we are:
+        // a. Building the Personal Tab.
+        // b. CloneProfile exists on the device.
+        if (userHandle.equals(getPersonalProfileUserHandle())
+                && getCloneProfileUserHandle() != null) {
+            userList.add(getCloneProfileUserHandle());
+        }
+        return userList;
+    }
 }
diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java
index 3e8f64b..a390016 100644
--- a/core/java/com/android/internal/app/ResolverComparatorModel.java
+++ b/core/java/com/android/internal/app/ResolverComparatorModel.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.app;
 
-import android.content.ComponentName;
 import android.content.pm.ResolveInfo;
 
+import com.android.internal.app.chooser.TargetInfo;
+
 import java.util.Comparator;
-import java.util.List;
 
 /**
  * A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
@@ -45,7 +45,7 @@
      * likelihood that the user will select that component as the target. Implementations that don't
      * assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
      */
-    float getScore(ComponentName name);
+    float getScore(TargetInfo targetInfo);
 
     /**
      * Notify the model that the user selected a target. (Models may log this information, use it as
@@ -53,5 +53,5 @@
      * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
      * instance in order to see any changes in the ranking that might result from this feedback.
      */
-    void notifyOnTargetSelected(ComponentName componentName);
+    void notifyOnTargetSelected(TargetInfo targetInfo);
 }
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 42b46cd..2df2b2b 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.PermissionChecker;
@@ -157,17 +156,17 @@
     /**
      * Returns the app share score of the given {@code componentName}.
      */
-    public float getScore(ComponentName componentName) {
-        return mResolverListController.getScore(componentName);
+    public float getScore(TargetInfo targetInfo) {
+        return mResolverListController.getScore(targetInfo);
     }
 
-    public void updateModel(ComponentName componentName) {
-        mResolverListController.updateModel(componentName);
+    public void updateModel(TargetInfo targetInfo) {
+        mResolverListController.updateModel(targetInfo);
     }
 
-    public void updateChooserCounts(String packageName, String action) {
+    public void updateChooserCounts(String packageName, String action, UserHandle userHandle) {
         mResolverListController.updateChooserCounts(
-                packageName, getUserHandle().getIdentifier(), action);
+                packageName, userHandle, action);
     }
 
     List<ResolvedComponentInfo> getUnfilteredResolveList() {
@@ -440,6 +439,7 @@
                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                         ri.icon = li.getIconResource();
                         ri.iconResourceId = ri.icon;
+                        ri.userHandle = getUserHandle();
                     }
                     if (userManager.isManagedProfile()) {
                         ri.noResourceId = true;
@@ -737,8 +737,10 @@
     }
 
     Drawable loadIconForResolveInfo(ResolveInfo ri) {
-        // Load icons based on the current process. If in work profile icons should be badged.
-        return makePresentationGetter(ri).getIcon(getUserHandle());
+        // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons
+        // should be badged.
+        return makePresentationGetter(ri)
+                .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, getUserHandle()));
     }
 
     void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 01dcf962..d9a19b0 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -60,6 +61,7 @@
 
     private AbstractResolverComparator mResolverComparator;
     private boolean isComputed = false;
+    private final UserHandle mQueryIntentsAsUser;
 
     public ResolverListController(
             Context context,
@@ -67,10 +69,17 @@
             Intent targetIntent,
             String referrerPackage,
             int launchedFromUid,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            UserHandle queryIntentsAsUser) {
         this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
                     new ResolverRankerServiceResolverComparator(
-                        context, targetIntent, referrerPackage, null, null));
+                            context,
+                            targetIntent,
+                            referrerPackage,
+                            null,
+                            null,
+                            userHandle),
+                queryIntentsAsUser);
     }
 
     public ResolverListController(
@@ -80,7 +89,8 @@
             String referrerPackage,
             int launchedFromUid,
             UserHandle userHandle,
-            AbstractResolverComparator resolverComparator) {
+            AbstractResolverComparator resolverComparator,
+            UserHandle queryIntentsAsUser) {
         mContext = context;
         mpm = pm;
         mLaunchedFromUid = launchedFromUid;
@@ -88,6 +98,7 @@
         mReferrerPackage = referrerPackage;
         mUserHandle = userHandle;
         mResolverComparator = resolverComparator;
+        mQueryIntentsAsUser = queryIntentsAsUser;
     }
 
     @VisibleForTesting
@@ -113,7 +124,7 @@
             boolean shouldGetOnlyDefaultActivities,
             List<Intent> intents) {
         return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata,
-                shouldGetOnlyDefaultActivities, intents, mUserHandle);
+                shouldGetOnlyDefaultActivities, intents, mQueryIntentsAsUser);
     }
 
     public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser(
@@ -126,7 +137,8 @@
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
-                | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
+                | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)
+                | PackageManager.MATCH_CLONE_PROFILE;
         return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags);
     }
 
@@ -170,6 +182,10 @@
         final int intoCount = into.size();
         for (int i = 0; i < fromCount; i++) {
             final ResolveInfo newInfo = from.get(i);
+            if (newInfo.userHandle == null) {
+                Log.w(TAG, "Skipping ResolveInfo with no userHandle: " + newInfo);
+                continue;
+            }
             boolean found = false;
             // Only loop to the end of into as it was before we started; no dupes in from.
             for (int j = 0; j < intoCount; j++) {
@@ -388,22 +404,22 @@
 
     @VisibleForTesting
     public float getScore(DisplayResolveInfo target) {
-        return mResolverComparator.getScore(target.getResolvedComponentName());
+        return mResolverComparator.getScore(target);
     }
 
     /**
      * Returns the app share score of the given {@code componentName}.
      */
-    public float getScore(ComponentName componentName) {
-        return mResolverComparator.getScore(componentName);
+    public float getScore(TargetInfo targetInfo) {
+        return mResolverComparator.getScore(targetInfo);
     }
 
-    public void updateModel(ComponentName componentName) {
-        mResolverComparator.updateModel(componentName);
+    public void updateModel(TargetInfo targetInfo) {
+        mResolverComparator.updateModel(targetInfo);
     }
 
-    public void updateChooserCounts(String packageName, int userId, String action) {
-        mResolverComparator.updateChooserCounts(packageName, userId, action);
+    public void updateChooserCounts(String packageName, UserHandle user, String action) {
+        mResolverComparator.updateChooserCounts(packageName, user, action);
     }
 
     public void destroy() {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 9922051..1ecaf21 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -41,9 +41,10 @@
             ResolverListAdapter adapter,
             EmptyStateProvider emptyStateProvider,
             QuietModeManager quietModeManager,
-            UserHandle workProfileUserHandle) {
+            UserHandle workProfileUserHandle,
+            UserHandle cloneUserHandle) {
         super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
-                workProfileUserHandle);
+                workProfileUserHandle, cloneUserHandle);
         mItems = new ResolverProfileDescriptor[] {
                 createProfileDescriptor(adapter)
         };
@@ -55,9 +56,10 @@
             EmptyStateProvider emptyStateProvider,
             QuietModeManager quietModeManager,
             @Profile int defaultProfile,
-            UserHandle workProfileUserHandle) {
+            UserHandle workProfileUserHandle,
+            UserHandle cloneUserHandle) {
         super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
-                workProfileUserHandle);
+                workProfileUserHandle, cloneUserHandle);
         mItems = new ResolverProfileDescriptor[] {
                 createProfileDescriptor(personalAdapter),
                 createProfileDescriptor(workAdapter)
@@ -107,11 +109,12 @@
     @Override
     @Nullable
     ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
-        if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
-            return getActiveListAdapter();
-        } else if (getInactiveListAdapter() != null
-                && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
-            return getInactiveListAdapter();
+        if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+                || userHandle.equals(getCloneUserHandle())) {
+            return getPersonalListAdapter();
+        } else if (getWorkListAdapter() != null
+                && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+            return getWorkListAdapter();
         }
         return null;
     }
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index e7f80a7..78c453d 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -17,11 +17,13 @@
 
 package com.android.internal.app;
 
+import android.annotation.Nullable;
 import android.app.usage.UsageStats;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -38,12 +40,16 @@
 import android.util.Log;
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import com.google.android.collect.Lists;
+
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,10 +75,10 @@
     private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
 
     private final Collator mCollator;
-    private final Map<String, UsageStats> mStats;
+    private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser;
     private final long mCurrentTime;
     private final long mSinceTime;
-    private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
+    private final Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> mTargetsDictPerUser;
     private final String mReferrerPackage;
     private final Object mLock = new Object();
     private ArrayList<ResolverTarget> mTargets;
@@ -85,17 +91,34 @@
     private CountDownLatch mConnectSignal;
     private ResolverRankerServiceComparatorModel mComparatorModel;
 
-    public ResolverRankerServiceResolverComparator(Context context, Intent intent,
+    // context here refers to the activity calling this comparator.
+    // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+    public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
                 String referrerPackage, AfterCompute afterCompute,
-                ChooserActivityLogger chooserActivityLogger) {
-        super(context, intent);
-        mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
-        mReferrerPackage = referrerPackage;
-        mContext = context;
+                ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace) {
+        this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger,
+                Lists.newArrayList(targetUserSpace));
+    }
 
+    // context here refers to the activity calling this comparator.
+    // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+    public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
+            String referrerPackage, AfterCompute afterCompute,
+            ChooserActivityLogger chooserActivityLogger, List<UserHandle> targetUserSpaceList) {
+        super(launchedFromContext, intent, targetUserSpaceList);
+        mCollator = Collator.getInstance(launchedFromContext
+                .getResources().getConfiguration().locale);
+        mReferrerPackage = referrerPackage;
+        mContext = launchedFromContext;
         mCurrentTime = System.currentTimeMillis();
         mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
-        mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
+        mStatsPerUser = new HashMap<>();
+        mTargetsDictPerUser = new HashMap<>();
+        for (UserHandle user : targetUserSpaceList) {
+            mStatsPerUser.put(user, mUsmMap.get(user)
+                    .queryAndAggregateUsageStats(mSinceTime, mCurrentTime));
+            mTargetsDictPerUser.put(user, new LinkedHashMap<>());
+        }
         mAction = intent.getAction();
         mRankerServiceName = new ComponentName(mContext, this.getClass());
         setCallBack(afterCompute);
@@ -147,57 +170,63 @@
 
         for (ResolvedComponentInfo target : targets) {
             final ResolverTarget resolverTarget = new ResolverTarget();
-            mTargetsDict.put(target.name, resolverTarget);
-            final UsageStats pkStats = mStats.get(target.name.getPackageName());
-            if (pkStats != null) {
-                // Only count recency for apps that weren't the caller
-                // since the caller is always the most recent.
-                // Persistent processes muck this up, so omit them too.
-                if (!target.name.getPackageName().equals(mReferrerPackage)
-                        && !isPersistentProcess(target)) {
-                    final float recencyScore =
-                            (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
-                    resolverTarget.setRecencyScore(recencyScore);
-                    if (recencyScore > mostRecencyScore) {
-                        mostRecencyScore = recencyScore;
-                    }
-                }
-                final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
-                resolverTarget.setTimeSpentScore(timeSpentScore);
-                if (timeSpentScore > mostTimeSpentScore) {
-                    mostTimeSpentScore = timeSpentScore;
-                }
-                final float launchScore = (float) pkStats.mLaunchCount;
-                resolverTarget.setLaunchScore(launchScore);
-                if (launchScore > mostLaunchScore) {
-                    mostLaunchScore = launchScore;
-                }
-
-                float chooserScore = 0.0f;
-                if (pkStats.mChooserCounts != null && mAction != null
-                        && pkStats.mChooserCounts.get(mAction) != null) {
-                    chooserScore = (float) pkStats.mChooserCounts.get(mAction)
-                            .getOrDefault(mContentType, 0);
-                    if (mAnnotations != null) {
-                        final int size = mAnnotations.length;
-                        for (int i = 0; i < size; i++) {
-                            chooserScore += (float) pkStats.mChooserCounts.get(mAction)
-                                    .getOrDefault(mAnnotations[i], 0);
+            final LinkedHashMap<ComponentName, ResolverTarget> targetsDict = mTargetsDictPerUser
+                    .get(target.getResolveInfoAt(0).userHandle);
+            final Map<String, UsageStats> stats = mStatsPerUser
+                    .get(target.getResolveInfoAt(0).userHandle);
+            if (targetsDict != null && stats != null) {
+                targetsDict.put(target.name, resolverTarget);
+                final UsageStats pkStats = stats.get(target.name.getPackageName());
+                if (pkStats != null) {
+                    // Only count recency for apps that weren't the caller
+                    // since the caller is always the most recent.
+                    // Persistent processes muck this up, so omit them too.
+                    if (!target.name.getPackageName().equals(mReferrerPackage)
+                            && !isPersistentProcess(target)) {
+                        final float recencyScore =
+                                (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
+                        resolverTarget.setRecencyScore(recencyScore);
+                        if (recencyScore > mostRecencyScore) {
+                            mostRecencyScore = recencyScore;
                         }
                     }
-                }
-                if (DEBUG) {
-                    if (mAction == null) {
-                        Log.d(TAG, "Action type is null");
-                    } else {
-                        Log.d(TAG, "Chooser Count of " + mAction + ":" +
-                                target.name.getPackageName() + " is " +
-                                Float.toString(chooserScore));
+                    final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
+                    resolverTarget.setTimeSpentScore(timeSpentScore);
+                    if (timeSpentScore > mostTimeSpentScore) {
+                        mostTimeSpentScore = timeSpentScore;
                     }
-                }
-                resolverTarget.setChooserScore(chooserScore);
-                if (chooserScore > mostChooserScore) {
-                    mostChooserScore = chooserScore;
+                    final float launchScore = (float) pkStats.mLaunchCount;
+                    resolverTarget.setLaunchScore(launchScore);
+                    if (launchScore > mostLaunchScore) {
+                        mostLaunchScore = launchScore;
+                    }
+
+                    float chooserScore = 0.0f;
+                    if (pkStats.mChooserCounts != null && mAction != null
+                            && pkStats.mChooserCounts.get(mAction) != null) {
+                        chooserScore = (float) pkStats.mChooserCounts.get(mAction)
+                                .getOrDefault(mContentType, 0);
+                        if (mAnnotations != null) {
+                            final int size = mAnnotations.length;
+                            for (int i = 0; i < size; i++) {
+                                chooserScore += (float) pkStats.mChooserCounts.get(mAction)
+                                        .getOrDefault(mAnnotations[i], 0);
+                            }
+                        }
+                    }
+                    if (DEBUG) {
+                        if (mAction == null) {
+                            Log.d(TAG, "Action type is null");
+                        } else {
+                            Log.d(TAG, "Chooser Count of " + mAction + ":"
+                                    + target.name.getPackageName() + " is "
+                                    + Float.toString(chooserScore));
+                        }
+                    }
+                    resolverTarget.setChooserScore(chooserScore);
+                    if (chooserScore > mostChooserScore) {
+                        mostChooserScore = chooserScore;
+                    }
                 }
             }
         }
@@ -209,7 +238,11 @@
                     + " mostChooserScore: " + mostChooserScore);
         }
 
-        mTargets = new ArrayList<>(mTargetsDict.values());
+        mTargets = new ArrayList<>();
+        for (UserHandle u : mTargetsDictPerUser.keySet()) {
+            mTargets.addAll(mTargetsDictPerUser.get(u).values());
+        }
+
         for (ResolverTarget target : mTargets) {
             final float recency = target.getRecencyScore() / mostRecencyScore;
             setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
@@ -232,15 +265,15 @@
     }
 
     @Override
-    public float getScore(ComponentName name) {
-        return mComparatorModel.getScore(name);
+    public float getScore(TargetInfo targetInfo) {
+        return mComparatorModel.getScore(targetInfo);
     }
 
     // update ranking model when the connection to it is valid.
     @Override
-    public void updateModel(ComponentName componentName) {
+    public void updateModel(TargetInfo targetInfo) {
         synchronized (mLock) {
-            mComparatorModel.notifyOnTargetSelected(componentName);
+            mComparatorModel.notifyOnTargetSelected(targetInfo);
         }
     }
 
@@ -281,7 +314,8 @@
     // resolve the service for ranking.
     private Intent resolveRankerService() {
         Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
-        final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
+        final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+                        .queryIntentServices(intent, 0);
         for (ResolveInfo resolveInfo : resolveInfos) {
             if (resolveInfo == null || resolveInfo.serviceInfo == null
                     || resolveInfo.serviceInfo.applicationInfo == null) {
@@ -294,7 +328,8 @@
                     resolveInfo.serviceInfo.applicationInfo.packageName,
                     resolveInfo.serviceInfo.name);
             try {
-                final String perm = mPm.getServiceInfo(componentName, 0).permission;
+                final String perm = mContext.getPackageManager()
+                        .getServiceInfo(componentName, 0).permission;
                 if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
                     Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
                             + " permission " + ResolverRankerService.BIND_PERMISSION
@@ -305,9 +340,9 @@
                             + " in the manifest.");
                     continue;
                 }
-                if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission(
-                        ResolverRankerService.HOLD_PERMISSION,
-                        resolveInfo.serviceInfo.packageName)) {
+                if (PackageManager.PERMISSION_GRANTED != mContext.getPackageManager()
+                        .checkPermission(ResolverRankerService.HOLD_PERMISSION,
+                            resolveInfo.serviceInfo.packageName)) {
                     Log.w(TAG, "ResolverRankerService " + componentName + " does not hold"
                             + " permission " + ResolverRankerService.HOLD_PERMISSION
                             + " - this service will not be queried for "
@@ -385,7 +420,9 @@
     @Override
     void beforeCompute() {
         super.beforeCompute();
-        mTargetsDict.clear();
+        for (UserHandle userHandle : mTargetsDictPerUser.keySet()) {
+            mTargetsDictPerUser.get(userHandle).clear();
+        }
         mTargets = null;
         mRankerServiceName = new ComponentName(mContext, this.getClass());
         mComparatorModel = buildUpdatedModel();
@@ -465,14 +502,14 @@
         // so the ResolverComparatorModel may provide inconsistent results. We should make immutable
         // copies of the data (waiting for any necessary remaining data before creating the model).
         return new ResolverRankerServiceComparatorModel(
-                mStats,
-                mTargetsDict,
+                mStatsPerUser,
+                mTargetsDictPerUser,
                 mTargets,
                 mCollator,
                 mRanker,
                 mRankerServiceName,
                 (mAnnotations != null),
-                mPm);
+                mPmMap);
     }
 
     /**
@@ -481,35 +518,36 @@
      * removing the complex legacy API.
      */
     static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel {
-        private final Map<String, UsageStats> mStats;  // Treat as immutable.
-        private final Map<ComponentName, ResolverTarget> mTargetsDict;  // Treat as immutable.
+        private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser; // Treat as immutable.
+        private final Map<UserHandle, LinkedHashMap<ComponentName,
+                ResolverTarget>> mTargetsDictPerUser; // Treat as immutable.
         private final List<ResolverTarget> mTargets;  // Treat as immutable.
         private final Collator mCollator;
         private final IResolverRankerService mRanker;
         private final ComponentName mRankerServiceName;
         private final boolean mAnnotationsUsed;
-        private final PackageManager mPm;
+        private final Map<UserHandle, PackageManager> mPmMap;
 
         // TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's
         // not written in a way that makes it clear whether we can derive one from the other (at
         // least in this constructor).
         ResolverRankerServiceComparatorModel(
-                Map<String, UsageStats> stats,
-                Map<ComponentName, ResolverTarget> targetsDict,
+                Map<UserHandle, Map<String, UsageStats>> statsPerUser,
+                Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> targetsDictPerUser,
                 List<ResolverTarget> targets,
                 Collator collator,
                 IResolverRankerService ranker,
                 ComponentName rankerServiceName,
                 boolean annotationsUsed,
-                PackageManager pm) {
-            mStats = stats;
-            mTargetsDict = targetsDict;
+                Map<UserHandle, PackageManager> pmMap) {
+            mStatsPerUser = statsPerUser;
+            mTargetsDictPerUser = targetsDictPerUser;
             mTargets = targets;
             mCollator = collator;
             mRanker = ranker;
             mRankerServiceName = rankerServiceName;
             mAnnotationsUsed = annotationsUsed;
-            mPm = pm;
+            mPmMap = pmMap;
         }
 
         @Override
@@ -518,25 +556,29 @@
             // a bug there, or do we have a way of knowing it will be non-null under certain
             // conditions?
             return (lhs, rhs) -> {
-                if (mStats != null) {
-                    final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
-                            lhs.activityInfo.packageName, lhs.activityInfo.name));
-                    final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
-                            rhs.activityInfo.packageName, rhs.activityInfo.name));
+                final ResolverTarget lhsTarget = getActivityResolverTargetForUser(lhs.activityInfo,
+                        lhs.userHandle);
+                final ResolverTarget rhsTarget = getActivityResolverTargetForUser(rhs.activityInfo,
+                        rhs.userHandle);
 
-                    if (lhsTarget != null && rhsTarget != null) {
-                        final int selectProbabilityDiff = Float.compare(
-                                rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+                if (lhsTarget != null && rhsTarget != null) {
+                    final int selectProbabilityDiff = Float.compare(
+                            rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
 
-                        if (selectProbabilityDiff != 0) {
-                            return selectProbabilityDiff > 0 ? 1 : -1;
-                        }
+                    if (selectProbabilityDiff != 0) {
+                        return selectProbabilityDiff > 0 ? 1 : -1;
                     }
                 }
 
-                CharSequence  sa = lhs.loadLabel(mPm);
+                CharSequence sa = null;
+                if (mPmMap.containsKey(lhs.userHandle)) {
+                    sa = lhs.loadLabel(mPmMap.get(lhs.userHandle));
+                }
                 if (sa == null) sa = lhs.activityInfo.name;
-                CharSequence  sb = rhs.loadLabel(mPm);
+                CharSequence sb = null;
+                if (mPmMap.containsKey(rhs.userHandle)) {
+                    sb = rhs.loadLabel(mPmMap.get(rhs.userHandle));
+                }
                 if (sb == null) sb = rhs.activityInfo.name;
 
                 return mCollator.compare(sa.toString().trim(), sb.toString().trim());
@@ -544,22 +586,28 @@
         }
 
         @Override
-        public float getScore(ComponentName name) {
-            final ResolverTarget target = mTargetsDict.get(name);
-            if (target != null) {
-                return target.getSelectProbability();
+        public float getScore(TargetInfo targetInfo) {
+            if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)
+                    && mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+                    .get(targetInfo.getResolvedComponentName()) != null) {
+                return mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+                        .get(targetInfo.getResolvedComponentName()).getSelectProbability();
             }
             return 0;
         }
 
         @Override
-        public void notifyOnTargetSelected(ComponentName componentName) {
+        public void notifyOnTargetSelected(TargetInfo targetInfo) {
             if (mRanker != null) {
                 try {
-                    int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
-                            .indexOf(componentName);
+                    int selectedPos = -1;
+                    if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)) {
+                        selectedPos = new ArrayList<>(mTargetsDictPerUser
+                                .get(targetInfo.getResolveInfo().userHandle).keySet())
+                                .indexOf(targetInfo.getResolvedComponentName());
+                    }
                     if (selectedPos >= 0 && mTargets != null) {
-                        final float selectedProbability = getScore(componentName);
+                        final float selectedProbability = getScore(targetInfo);
                         int order = 0;
                         for (ResolverTarget target : mTargets) {
                             if (target.getSelectProbability() > selectedProbability) {
@@ -570,7 +618,8 @@
                         mRanker.train(mTargets, selectedPos);
                     } else {
                         if (DEBUG) {
-                            Log.d(TAG, "Selected a unknown component: " + componentName);
+                            Log.d(TAG, "Selected a unknown component: " + targetInfo
+                                    .getResolvedComponentName());
                         }
                     }
                 } catch (RemoteException e) {
@@ -594,5 +643,16 @@
                 metricsLogger.write(log);
             }
         }
+
+        @Nullable
+        private ResolverTarget getActivityResolverTargetForUser(
+                ActivityInfo activity, UserHandle user) {
+            if ((mStatsPerUser == null) || !mTargetsDictPerUser.containsKey(user)) {
+                return null;
+            }
+            return mTargetsDictPerUser
+                    .get(user)
+                    .get(new ComponentName(activity.packageName, activity.name));
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
index 9a6d4c1..416f510 100644
--- a/core/java/com/android/internal/app/SystemLocaleCollector.java
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -26,9 +26,15 @@
 /** The Locale data collector for System language. */
 class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
     private final Context mContext;
+    private LocaleList mExplicitLocales;
 
     SystemLocaleCollector(Context context) {
+        this(context, null);
+    }
+
+    SystemLocaleCollector(Context context, LocaleList explicitLocales) {
         mContext = context;
+        mExplicitLocales = explicitLocales;
     }
 
     @Override
@@ -47,18 +53,16 @@
             boolean translatedOnly, boolean isForCountryMode) {
         Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
         Set<LocaleStore.LocaleInfo> localeList;
-
         if (isForCountryMode) {
             localeList = LocaleStore.getLevelLocales(mContext,
-                    langTagsToIgnore, parent, translatedOnly);
+                    langTagsToIgnore, parent, translatedOnly, mExplicitLocales);
         } else {
             localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
-                    null /* no parent */, translatedOnly);
+                    null /* no parent */, translatedOnly, mExplicitLocales);
         }
         return localeList;
     }
 
-
     @Override
     public boolean hasSpecificPackageName() {
         return false;
diff --git a/core/java/com/android/internal/content/InstallLocationUtils.java b/core/java/com/android/internal/content/InstallLocationUtils.java
index 4d9c09e..a173ce1 100644
--- a/core/java/com/android/internal/content/InstallLocationUtils.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -292,7 +292,7 @@
 
         // For new installations of a predefined size, check property to let it through
         // regardless of the actual free space.
-        if (bestCandidate != null && Integer.MAX_VALUE == params.sizeBytes
+        if (!volumePaths.isEmpty() && Integer.MAX_VALUE == params.sizeBytes
                 && SystemProperties.getBoolean("debug.pm.install_skip_size_check_for_maxint",
                 false)) {
             return bestCandidate;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 95a47ea..d9e9a5f 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,15 +16,11 @@
 
 package com.android.internal.jank;
 
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
@@ -93,19 +89,17 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
 import android.annotation.UiThread;
 import android.annotation.WorkerThread;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -238,7 +232,6 @@
     public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_IME_INSETS_ANIMATION = 69;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -316,7 +309,6 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
     };
 
     private static class InstanceHolder {
@@ -409,8 +401,7 @@
             CUJ_RECENTS_SCROLLING,
             CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
-            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_IME_INSETS_ANIMATION,
+            CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -431,37 +422,29 @@
      * @param worker the worker thread for the callbacks
      */
     @VisibleForTesting
-    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
+        // Check permission early.
+        Settings.Config.enforceReadPermission(
+            DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
+
         mRunningTrackers = new SparseArray<>();
         mTimeoutActions = new SparseArray<>();
         mWorker = worker;
         mWorker.start();
-        mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
         mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
-        mEnabled = DEFAULT_ENABLED;
+        mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
 
-        final Context context = ActivityThread.currentApplication();
-        if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            mWorker.getThreadHandler().post(
-                    () -> mPropertiesChangedListener.onPropertiesChanged(
-                            DeviceConfig.getProperties(
-                                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
-            DeviceConfig.addOnPropertiesChangedListener(
-                    DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
-                    new HandlerExecutor(mWorker.getThreadHandler()),
-                    mPropertiesChangedListener);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "Initialized the InteractionJankMonitor."
-                        + " (No READ_DEVICE_CONFIG permission to change configs)"
-                        + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
-                        + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
-                        + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
-                        + ", package=" + context.getPackageName());
-            }
-        }
+        // Post initialization to the background in case we're running on the main
+        // thread.
+        mWorker.getThreadHandler().post(
+                () -> mPropertiesChangedListener.onPropertiesChanged(
+                        DeviceConfig.getProperties(
+                                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+                new HandlerExecutor(mWorker.getThreadHandler()),
+                mPropertiesChangedListener);
+        mEnabled = DEFAULT_ENABLED;
     }
 
     /**
@@ -940,8 +923,6 @@
                 return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
             case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
                 return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_IME_INSETS_ANIMATION:
-                return "IME_INSETS_ANIMATION";
         }
         return "UNKNOWN";
     }
@@ -1197,7 +1178,7 @@
          */
         @VisibleForTesting
         public int getDisplayId() {
-            return mSurfaceOnly ? mContext.getDisplayId() : mView.getContext().getDisplayId();
+            return (mSurfaceOnly ? mContext.getDisplay() : mView.getDisplay()).getDisplayId();
         }
     }
 
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index af205d2..40d5c47 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -50,9 +50,6 @@
     public static final boolean CONFIG_SMALL_BATTERY =
             SystemProperties.getBoolean("ro.config.small_battery", false);
 
-    // ------ ro.fw.* ------------ //
-    public static final boolean FW_SYSTEM_USER_SPLIT =
-            SystemProperties.getBoolean("ro.fw.system_user_split", false);
     /**
      * Indicates whether the device should run in headless system user mode,
      *   in which user 0 only runs the system, not a real user.
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8f943ef..ad1fdba 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -79,7 +79,9 @@
             Consts.TAG_WM),
     WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
-    WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+    WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM),
+    WM_DEBUG_WINDOW_TRANSITIONS_MIN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM),
     WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM),
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 2d5bb6c..afb526a 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,10 +14,7 @@
 
 package com.android.internal.util;
 
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Trace.TRACE_TAG_APP;
-import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
 
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
@@ -27,8 +24,6 @@
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
-import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN;
-import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
 import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
@@ -47,12 +42,9 @@
 import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
 import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.ActivityThread;
 import android.content.Context;
 import android.os.Build;
 import android.os.ConditionVariable;
@@ -195,16 +187,6 @@
      */
     public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
 
-    /**
-     * Time it takes to request IME shown animation.
-     */
-    public static final int ACTION_REQUEST_IME_SHOWN = 20;
-
-    /**
-     * Time it takes to request IME hidden animation.
-     */
-    public static final int ACTION_REQUEST_IME_HIDDEN = 21;
-
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -226,8 +208,6 @@
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
         ACTION_SHOW_VOICE_INTERACTION,
-        ACTION_REQUEST_IME_SHOWN,
-        ACTION_REQUEST_IME_HIDDEN,
     };
 
     /** @hide */
@@ -252,8 +232,6 @@
         ACTION_SHOW_SELECTION_TOOLBAR,
         ACTION_FOLD_TO_AOD,
         ACTION_SHOW_VOICE_INTERACTION,
-        ACTION_REQUEST_IME_SHOWN,
-        ACTION_REQUEST_IME_HIDDEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -281,8 +259,6 @@
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
             UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
-            UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN,
-            UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -308,30 +284,15 @@
         return sLatencyTracker;
     }
 
-    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @VisibleForTesting
     public LatencyTracker() {
         mEnabled = DEFAULT_ENABLED;
 
-        final Context context = ActivityThread.currentApplication();
-        if (context != null
-                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            BackgroundThread.getHandler().post(() -> this.updateProperties(
-                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
-                    BackgroundThread.getExecutor(), this::updateProperties);
-        } else {
-            if (DEBUG) {
-                if (context == null) {
-                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
-                } else {
-                    Log.d(TAG, "Initialized the LatencyTracker."
-                            + " (No READ_DEVICE_CONFIG permission to change configs)"
-                            + " enabled=" + mEnabled + ", package=" + context.getPackageName());
-                }
-            }
-        }
+        // Post initialization to the background in case we're running on the main thread.
+        BackgroundThread.getHandler().post(() -> this.updateProperties(
+                DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+                BackgroundThread.getExecutor(), this::updateProperties);
     }
 
     private void updateProperties(DeviceConfig.Properties properties) {
@@ -407,10 +368,6 @@
                 return "ACTION_FOLD_TO_AOD";
             case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
                 return "ACTION_SHOW_VOICE_INTERACTION";
-            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN:
-                return "ACTION_REQUEST_IME_SHOWN";
-            case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN:
-                return "ACTION_REQUEST_IME_HIDDEN";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 79c5196..3a393b6 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,7 @@
 package com.android.internal.util;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -11,29 +11,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.Insets;
-import android.graphics.ParcelableColorSpace;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.WindowManager.ScreenshotSource;
-import android.view.WindowManager.ScreenshotType;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Objects;
 import java.util.function.Consumer;
 
 public class ScreenshotHelper {
@@ -41,212 +30,6 @@
     public static final int SCREENSHOT_MSG_URI = 1;
     public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
 
-    /**
-     * Describes a screenshot request.
-     */
-    public static class ScreenshotRequest implements Parcelable {
-        @ScreenshotType
-        private final int mType;
-
-        @ScreenshotSource
-        private final int mSource;
-
-        private final Bundle mBitmapBundle;
-        private final Rect mBoundsInScreen;
-        private final Insets mInsets;
-        private final int mTaskId;
-        private final int mUserId;
-        private final ComponentName mTopComponent;
-
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
-            this(type, source, /* topComponent */ null);
-        }
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
-                ComponentName topComponent) {
-            this(type,
-                source,
-                /* bitmapBundle*/ null,
-                /* boundsInScreen */ null,
-                /* insets */ null,
-                /* taskId */ -1,
-                /* userId */ -1,
-                topComponent);
-        }
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
-                Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
-                ComponentName topComponent) {
-            mType = type;
-            mSource = source;
-            mBitmapBundle = bitmapBundle;
-            mBoundsInScreen = boundsInScreen;
-            mInsets = insets;
-            mTaskId = taskId;
-            mUserId = userId;
-            mTopComponent = topComponent;
-        }
-
-        ScreenshotRequest(Parcel in) {
-            mType = in.readInt();
-            mSource = in.readInt();
-            if (in.readInt() == 1) {
-                mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
-                mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
-                mTaskId = in.readInt();
-                mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
-                        ComponentName.class);
-            } else {
-                mBitmapBundle = null;
-                mBoundsInScreen = null;
-                mInsets = null;
-                mTaskId = -1;
-                mUserId = -1;
-                mTopComponent = null;
-            }
-        }
-
-        @ScreenshotType
-        public int getType() {
-            return mType;
-        }
-
-        @ScreenshotSource
-        public int getSource() {
-            return mSource;
-        }
-
-        public Bundle getBitmapBundle() {
-            return mBitmapBundle;
-        }
-
-        public Rect getBoundsInScreen() {
-            return mBoundsInScreen;
-        }
-
-        public Insets getInsets() {
-            return mInsets;
-        }
-
-        public int getTaskId() {
-            return mTaskId;
-        }
-
-        public int getUserId() {
-            return mUserId;
-        }
-
-        public ComponentName getTopComponent() {
-            return mTopComponent;
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(mType);
-            dest.writeInt(mSource);
-            if (mBitmapBundle == null) {
-                dest.writeInt(0);
-            } else {
-                dest.writeInt(1);
-                dest.writeBundle(mBitmapBundle);
-                dest.writeParcelable(mBoundsInScreen, 0);
-                dest.writeParcelable(mInsets, 0);
-                dest.writeInt(mTaskId);
-                dest.writeInt(mUserId);
-                dest.writeParcelable(mTopComponent, 0);
-            }
-        }
-
-        @NonNull
-        public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
-                new Parcelable.Creator<ScreenshotRequest>() {
-
-                    @Override
-                    public ScreenshotRequest createFromParcel(Parcel source) {
-                        return new ScreenshotRequest(source);
-                    }
-
-                    @Override
-                    public ScreenshotRequest[] newArray(int size) {
-                        return new ScreenshotRequest[size];
-                    }
-                };
-    }
-
-    /**
-     * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
-     * content. This is expected to be used together with {@link #provideScreenshot} to handle a
-     * hardware bitmap as a screenshot.
-     */
-    public static final class HardwareBitmapBundler {
-        private static final String KEY_BUFFER = "bitmap_util_buffer";
-        private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
-
-        private HardwareBitmapBundler() {
-        }
-
-        /**
-         * Creates a Bundle that represents the given Bitmap.
-         * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
-         * copies when passing across processes, only pass to processes you trust.
-         *
-         * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
-         * returned Bundle should be treated as a standalone object.
-         *
-         * @param bitmap to convert to bundle
-         * @return a Bundle representing the bitmap, should only be parsed by
-         * {@link #bundleToHardwareBitmap(Bundle)}
-         */
-        public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
-            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
-                throw new IllegalArgumentException(
-                        "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
-            }
-
-            // Bitmap assumes SRGB for null color space
-            ParcelableColorSpace colorSpace =
-                    bitmap.getColorSpace() == null
-                            ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
-                            : new ParcelableColorSpace(bitmap.getColorSpace());
-
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
-            bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
-
-            return bundle;
-        }
-
-        /**
-         * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
-         *
-         * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
-         * this Bitmap on to any other source.
-         *
-         * @param bundle containing the bitmap
-         * @return a hardware Bitmap
-         */
-        public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
-            if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
-                throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
-            }
-
-            HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
-            ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
-                    ParcelableColorSpace.class);
-
-            return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
-                    colorSpace.getColorSpace());
-        }
-    }
-
     private static final String TAG = "ScreenshotHelper";
 
     // Time until we give up on the screenshot & show an error instead.
@@ -277,20 +60,35 @@
     /**
      * Request a screenshot be taken.
      * <p>
-     * Added to support reducing unit test duration; the method variant without a timeout argument
-     * is recommended for general use.
+     * Convenience method for taking a full screenshot with provided source.
      *
-     * @param type The type of screenshot, defined by {@link ScreenshotType}
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler used to process messages received from the screenshot service
+     * @param source             source of the screenshot request, defined by {@link
+     *                           ScreenshotSource}
+     * @param handler            used to process messages received from the screenshot service
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
+     *                           null if no screenshot was saved
      */
-    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
-            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
-        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
-                completionConsumer);
+    public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest request =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
+        takeScreenshot(request, handler, completionConsumer);
+    }
+
+    /**
+     * Request a screenshot be taken.
+     * <p>
+     *
+     * @param request            description of the screenshot request, either for taking a
+     *                           screenshot or
+     *                           providing a bitmap
+     * @param handler            used to process messages received from the screenshot service
+     * @param completionConsumer receives the URI of the captured screenshot, once saved or
+     *                           null if no screenshot was saved
+     */
+    public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
     }
 
     /**
@@ -299,46 +97,16 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param type The type of screenshot, defined by {@link ScreenshotType}
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler used to process messages received from the screenshot service
-     * @param timeoutMs time limit for processing, intended only for testing
+     * @param request            description of the screenshot request, either for taking a
+     *                           screenshot or providing a bitmap
+     * @param handler            used to process messages received from the screenshot service
+     * @param timeoutMs          time limit for processing, intended only for testing
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
+     *                           null if no screenshot was saved
      */
     @VisibleForTesting
-    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
-            @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
-        takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
-    }
-
-    /**
-     * Request that provided image be handled as if it was a screenshot.
-     *
-     * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
-     * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
-     * @param insets The insets that the image was shown with, inside the screen bounds.
-     * @param taskId The taskId of the task that the screen shot was taken of.
-     * @param userId The userId of user running the task provided in taskId.
-     * @param topComponent The component name of the top component running in the task.
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler A handler used in case the screenshot times out
-     * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
-     */
-    public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
-            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
-            @ScreenshotSource int source, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
-                source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
-        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
-    }
-
-    private void takeScreenshot(@NonNull Handler handler,
-            ScreenshotRequest screenshotRequest, long timeoutMs,
-            @Nullable Consumer<Uri> completionConsumer) {
+    public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
         synchronized (mScreenshotLock) {
 
             final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@
                 }
             };
 
-            Message msg = Message.obtain(null, 0, screenshotRequest);
+            Message msg = Message.obtain(null, 0, request);
 
             Handler h = new Handler(handler.getLooper()) {
                 @Override
@@ -471,5 +239,4 @@
                 Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
     }
-
 }
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.aidl b/core/java/com/android/internal/util/ScreenshotRequest.aidl
new file mode 100644
index 0000000..b08905d
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.internal.util;
+
+parcelable ScreenshotRequest;
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
new file mode 100644
index 0000000..1902f80
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2023 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Objects;
+
+/**
+ * Describes a screenshot request.
+ */
+public class ScreenshotRequest implements Parcelable {
+    private static final String TAG = "ScreenshotRequest";
+
+    @WindowManager.ScreenshotType
+    private final int mType;
+    @WindowManager.ScreenshotSource
+    private final int mSource;
+    private final ComponentName mTopComponent;
+    private final int mTaskId;
+    private final int mUserId;
+    private final Bitmap mBitmap;
+    private final Rect mBoundsInScreen;
+    private final Insets mInsets;
+
+    private ScreenshotRequest(
+            @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
+            ComponentName topComponent, int taskId, int userId,
+            Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+        mType = type;
+        mSource = source;
+        mTopComponent = topComponent;
+        mTaskId = taskId;
+        mUserId = userId;
+        mBitmap = bitmap;
+        mBoundsInScreen = boundsInScreen;
+        mInsets = insets;
+    }
+
+    ScreenshotRequest(Parcel in) {
+        mType = in.readInt();
+        mSource = in.readInt();
+        mTopComponent = in.readTypedObject(ComponentName.CREATOR);
+        mTaskId = in.readInt();
+        mUserId = in.readInt();
+        mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
+        mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
+        mInsets = in.readTypedObject(Insets.CREATOR);
+    }
+
+    @WindowManager.ScreenshotType
+    public int getType() {
+        return mType;
+    }
+
+    @WindowManager.ScreenshotSource
+    public int getSource() {
+        return mSource;
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public Rect getBoundsInScreen() {
+        return mBoundsInScreen;
+    }
+
+    public Insets getInsets() {
+        return mInsets;
+    }
+
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ComponentName getTopComponent() {
+        return mTopComponent;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mSource);
+        dest.writeTypedObject(mTopComponent, 0);
+        dest.writeInt(mTaskId);
+        dest.writeInt(mUserId);
+        dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
+        dest.writeTypedObject(mBoundsInScreen, 0);
+        dest.writeTypedObject(mInsets, 0);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
+            new Parcelable.Creator<ScreenshotRequest>() {
+
+                @Override
+                public ScreenshotRequest createFromParcel(Parcel source) {
+                    return new ScreenshotRequest(source);
+                }
+
+                @Override
+                public ScreenshotRequest[] newArray(int size) {
+                    return new ScreenshotRequest[size];
+                }
+            };
+
+    /**
+     * Builder class for {@link ScreenshotRequest} objects.
+     */
+    public static class Builder {
+        @WindowManager.ScreenshotType
+        private final int mType;
+
+        @WindowManager.ScreenshotSource
+        private final int mSource;
+
+        private Bitmap mBitmap;
+        private Rect mBoundsInScreen;
+        private Insets mInsets = Insets.NONE;
+        private int mTaskId = INVALID_TASK_ID;
+        private int mUserId = USER_NULL;
+        private ComponentName mTopComponent;
+
+        /**
+         * Begin building a ScreenshotRequest.
+         *
+         * @param type   The type of the screenshot request, defined by {@link
+         *               WindowManager.ScreenshotType}
+         * @param source The source of the screenshot request, defined by {@link
+         *               WindowManager.ScreenshotSource}
+         */
+        public Builder(
+                @WindowManager.ScreenshotType int type,
+                @WindowManager.ScreenshotSource int source) {
+            mType = type;
+            mSource = source;
+        }
+
+        /**
+         * Construct a new {@link ScreenshotRequest} with the set parameters.
+         */
+        public ScreenshotRequest build() {
+            if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
+                Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
+            }
+            if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
+                throw new IllegalStateException(
+                        "Request is PROVIDED_IMAGE, but no bitmap is provided!");
+            }
+
+            return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
+                    mBoundsInScreen, mInsets);
+        }
+
+        /**
+         * Set the top component associated with this request.
+         *
+         * @param topComponent The component name of the top component running in the task.
+         */
+        public Builder setTopComponent(ComponentName topComponent) {
+            mTopComponent = topComponent;
+            return this;
+        }
+
+        /**
+         * Set the task id associated with this request.
+         *
+         * @param taskId The taskId of the task that the screenshot was taken of.
+         */
+        public Builder setTaskId(int taskId) {
+            mTaskId = taskId;
+            return this;
+        }
+
+        /**
+         * Set the user id associated with this request.
+         *
+         * @param userId The userId of user running the task provided in taskId.
+         */
+        public Builder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /**
+         * Set the bitmap associated with this request.
+         *
+         * @param bitmap The provided screenshot.
+         */
+        public Builder setBitmap(Bitmap bitmap) {
+            mBitmap = bitmap;
+            return this;
+        }
+
+        /**
+         * Set the bounds for the provided bitmap.
+         *
+         * @param bounds The bounds in screen coordinates that the bitmap originated from.
+         */
+        public Builder setBoundsOnScreen(Rect bounds) {
+            mBoundsInScreen = bounds;
+            return this;
+        }
+
+        /**
+         * Set the insets for the provided bitmap.
+         *
+         * @param insets The insets that the image was shown with, inside the screen bounds.
+         */
+        public Builder setInsets(@NonNull Insets insets) {
+            mInsets = insets;
+            return this;
+        }
+    }
+
+    /**
+     * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
+     * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
+     * bitmap as a screenshot.
+     */
+    private static final class HardwareBitmapBundler {
+        private static final String KEY_BUFFER = "bitmap_util_buffer";
+        private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+        private HardwareBitmapBundler() {
+        }
+
+        /**
+         * Creates a Bundle that represents the given Bitmap.
+         * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
+         * avoid
+         * copies when passing across processes, only pass to processes you trust.
+         *
+         * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
+         * the
+         * returned Bundle should be treated as a standalone object.
+         *
+         * @param bitmap to convert to bundle
+         * @return a Bundle representing the bitmap, should only be parsed by
+         * {@link #bundleToHardwareBitmap(Bundle)}
+         */
+        private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+            if (bitmap == null) {
+                return null;
+            }
+            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+                throw new IllegalArgumentException(
+                        "Passed bitmap must have hardware config, found: "
+                                + bitmap.getConfig());
+            }
+
+            // Bitmap assumes SRGB for null color space
+            ParcelableColorSpace colorSpace =
+                    bitmap.getColorSpace() == null
+                            ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+                            : new ParcelableColorSpace(bitmap.getColorSpace());
+
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+            bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+            return bundle;
+        }
+
+        /**
+         * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
+         *
+         * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
+         * passing
+         * this Bitmap on to any other source.
+         *
+         * @param bundle containing the bitmap
+         * @return a hardware Bitmap
+         */
+        private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+            if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+                throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+            }
+
+            HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+            ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+                    ParcelableColorSpace.class);
+
+            return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
+                    colorSpace.getColorSpace());
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 4cf0ba1..635adca 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -927,7 +927,8 @@
                 message = messages.get(i - histSize);
             }
             boolean isNewGroup = currentGroup == null;
-            Person sender = message.getMessage().getSenderPerson();
+            Person sender =
+                    message.getMessage() == null ? null : message.getMessage().getSenderPerson();
             CharSequence key = getKey(sender);
             isNewGroup |= !TextUtils.equals(key, currentSenderKey);
             if (isNewGroup) {
@@ -1190,7 +1191,8 @@
             return null;
         }
         final MessagingMessage messagingMessage = mMessages.get(mMessages.size() - 1);
-        final CharSequence text = messagingMessage.getMessage().getText();
+        final CharSequence text = messagingMessage.getMessage() == null ? null
+                : messagingMessage.getMessage().getText();
         if (text == null && messagingMessage instanceof MessagingImageMessage) {
             final String unformatted =
                     getResources().getString(R.string.conversation_single_line_image_placeholder);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3c305f6..86fd956 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -619,12 +619,11 @@
         }
         boolean disabledByDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_disableLockscreenByDefault);
-        boolean isSystemUser = UserManager.isSplitSystemUser() && userId == UserHandle.USER_SYSTEM;
         UserInfo userInfo = getUserManager().getUserInfo(userId);
         boolean isDemoUser = UserManager.isDeviceInDemoMode(mContext) && userInfo != null
                 && userInfo.isDemo();
         return getBoolean(DISABLE_LOCKSCREEN_KEY, false, userId)
-                || (disabledByDefault && !isSystemUser)
+                || disabledByDefault
                 || isDemoUser;
     }
 
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 146cb3f..30e4099 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -492,7 +492,9 @@
             int color = mSendingSpinnerContainer.getVisibility() == View.VISIBLE
                     ? mSendingTextColor : mTextColor;
             for (MessagingMessage message : mMessages) {
-                message.setColor(message.getMessage().isRemoteInputHistory() ? color : mTextColor);
+                final boolean isRemoteInputHistory =
+                        message.getMessage() != null && message.getMessage().isRemoteInputHistory();
+                message.setColor(isRemoteInputHistory ? color : mTextColor);
             }
         }
     }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 67b671e..9d142f6 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -470,7 +470,8 @@
                 message = messages.get(i - histSize);
             }
             boolean isNewGroup = currentGroup == null;
-            Person sender = message.getMessage().getSenderPerson();
+            Person sender =
+                    message.getMessage() == null ? null : message.getMessage().getSenderPerson();
             CharSequence key = sender == null ? null
                     : sender.getKey() == null ? sender.getName() : sender.getKey();
             isNewGroup |= !TextUtils.equals(key, currentSenderKey);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 2cc0d23..5ecd3b8 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -68,6 +68,10 @@
 
     default boolean sameAs(Notification.MessagingStyle.Message message) {
         Notification.MessagingStyle.Message ownMessage = getMessage();
+        // We have to make sure both messages are not null to go further comparison
+        if (message == null || ownMessage == null) {
+            return message == ownMessage;
+        }
         if (!Objects.equals(message.getText(), ownMessage.getText())) {
             return false;
         }
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a68040d..bd85874 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -72,6 +72,7 @@
 per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
 per-file *HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
 per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_OverlayProperties.cpp = file:/graphics/java/android/graphics/OWNERS
 per-file android_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/OWNERS
 
 ### Text ###
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 7d379e5..0bc0878 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -68,6 +68,7 @@
     }
 
     const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+    const auto usiVersion = deviceInfo.getUsiVersion().value_or(InputDeviceUsiVersion{-1, -1});
 
     ScopedLocalRef<jobject>
             inputDeviceObj(env,
@@ -81,7 +82,9 @@
                                           keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(),
                                           deviceInfo.hasVibrator(), deviceInfo.hasMic(),
                                           deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
-                                          deviceInfo.hasBattery(), deviceInfo.supportsUsi()));
+                                          deviceInfo.hasBattery(), usiVersion.majorVersion,
+                                          usiVersion.minorVersion,
+                                          deviceInfo.getAssociatedDisplayId()));
     // Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
     // it to apps that do not have the Bluetooth permission.
 
@@ -105,7 +108,7 @@
     gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
                                                   "(IIILjava/lang/String;IILjava/lang/"
                                                   "String;ZIILandroid/view/KeyCharacterMap;Ljava/"
-                                                  "lang/String;Ljava/lang/String;ZZZZZZ)V");
+                                                  "lang/String;Ljava/lang/String;ZZZZZIII)V");
 
     gInputDeviceClassInfo.addMotionRange =
             GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index e165b07..fd0d9c5 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -160,7 +160,7 @@
     optional Bluetooth bluetooth = 21;
 
     optional SettingProto boot_count = 22 [ (android.privacy).dest = DEST_AUTOMATIC ];
-    optional SettingProto bugreport_in_power_menu = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    reserved 23; // Moved to secure settings bugreport_in_power_menu
     optional SettingProto cached_apps_freezer_enabled = 152 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto call_auto_retry = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index e6f942e..ea0ec79 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -186,6 +186,7 @@
     optional Backup backup = 10;
 
     optional SettingProto bluetooth_on_while_driving = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    optional SettingProto bugreport_in_power_menu = 95 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Camera {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -696,5 +697,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 95;
+    // Next tag = 96;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 27f2b8a..bc40cef 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7050,6 +7050,19 @@
     <permission android:name="android.permission.QUERY_CLONED_APPS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @SystemApi
+         Allows applications to capture bugreport directly without consent dialog when using the
+         bugreporting API on userdebug/eng build.
+         <p>The application still needs to hold {@link android.permission.DUMP} permission and be
+         bugreport-whitelisted to be able to capture a bugreport using the bugreporting API in the
+         first place. Then, when the corresponding app op of this permission is ALLOWED, the
+         bugreport can be captured directly without going through the consent dialog.
+         <p>Protection level: internal|appop
+         <p>Intended to only be used on userdebug/eng build.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"
+                android:protectionLevel="internal|appop" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0b5d66d..6616365 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5802,6 +5802,7 @@
            - config_roundedCornerDrawableArray (config in SystemUI resource)
            - config_roundedCornerTopDrawableArray (config in SystemUI resource)
            - config_roundedCornerBottomDrawableArray (config in SystemUI resource)
+           - config_displayUsiVersionArray
 
          Leave this array empty for single display device and the system will load the default main
          built-in related configs.
@@ -6160,4 +6161,21 @@
         trusted certificate using the SHA-256 digest algorithm. -->
     <string-array name="config_healthConnectMigrationKnownSigners">
     </string-array>
+
+    <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+         (@see https://universalstylus.org/).
+
+         The i-th value in this array corresponds to the supported USI version of the i-th display
+         listed in config_displayUniqueIdArray. On a single-display device, the
+         config_displayUniqueIdArray may be empty, in which case the only value in this array should
+         be the USI version for the main built-in display.
+
+         If the display does not support USI, the version value should be an empty string. If the
+         display supports USI, the version must be in the following format:
+           "<major-version>.<minor-version>"
+
+         For example, "", "1.0", and "2.0" are valid values. -->
+    <string-array name="config_displayUsiVersionArray" translatable="false">
+        <item>""</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daedde1..93d0873 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4779,6 +4779,7 @@
   <java-symbol type="array" name="config_mainBuiltInDisplayWaterfallCutout" />
   <java-symbol type="array" name="config_secondaryBuiltInDisplayWaterfallCutout" />
   <java-symbol type="array" name="config_waterfallCutoutArray" />
+  <java-symbol type="array" name="config_displayUsiVersionArray" />
 
   <java-symbol type="fraction" name="global_actions_vertical_padding_percentage" />
   <java-symbol type="fraction" name="global_actions_horizontal_padding_percentage" />
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
index 966d362..9acb99a 100644
--- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -92,9 +92,9 @@
         }
 
         fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
-                Intent(INTENT_ACTION),
+                Intent(INTENT_ACTION).setPackage(context.packageName),
                 PendingIntent.FLAG_UPDATE_CURRENT
-                        or PendingIntent.FLAG_MUTABLE_UNAUDITED).intentSender
+                        or PendingIntent.FLAG_MUTABLE).intentSender
 
         fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
 
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index bcb13d2..4548730 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -248,7 +248,9 @@
 
     @Test
     public void allPendingIntents_containsCustomRemoteViews() {
-        PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent intent = PendingIntent.getActivity(mContext, 0,
+                new Intent("test").setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
 
         RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
         contentView.setOnClickPendingIntent(1 /* id */, intent);
@@ -1578,7 +1580,8 @@
      * Creates a PendingIntent with the given action.
      */
     private PendingIntent createPendingIntent(String action) {
-        return PendingIntent.getActivity(mContext, 0, new Intent(action),
+        return PendingIntent.getActivity(mContext, 0,
+                new Intent(action).setPackage(mContext.getPackageName()),
                 PendingIntent.FLAG_MUTABLE);
     }
 }
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 05775bc..1b52f80 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -32,14 +32,16 @@
         registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED);
         addIntermediate("after-register");
         PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
-                makeBroadcastIntent(BROADCAST_REGISTERED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                makeBroadcastIntent(BROADCAST_REGISTERED).setPackage(getContext().getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         is.send();
         waitForResultOrThrow(BROADCAST_TIMEOUT);
         is.cancel();
     }
 
     public void testRegisteredReceivePermissionDenied() throws Exception {
-        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED)
+                .setPackage(getContext().getPackageName());
 
         setExpectedReceivers(new String[]{RECEIVER_RESULTS});
         registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED);
@@ -52,7 +54,8 @@
             }
         };
 
-        PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+                PendingIntent.FLAG_MUTABLE);
         is.send(Activity.RESULT_CANCELED, finish, null);
         waitForResultOrThrow(BROADCAST_TIMEOUT);
         is.cancel();
@@ -61,14 +64,16 @@
     public void testLocalReceivePermissionGranted() throws Exception {
         setExpectedReceivers(new String[]{RECEIVER_LOCAL});
         PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
-                makeBroadcastIntent(BROADCAST_LOCAL_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                makeBroadcastIntent(BROADCAST_LOCAL_GRANTED)
+                        .setPackage(getContext().getPackageName()), PendingIntent.FLAG_MUTABLE);
         is.send();
         waitForResultOrThrow(BROADCAST_TIMEOUT);
         is.cancel();
     }
 
     public void testLocalReceivePermissionDenied() throws Exception {
-        final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED);
+        final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED)
+                .setPackage(getContext().getPackageName());
 
         setExpectedReceivers(new String[]{RECEIVER_RESULTS});
 
@@ -79,7 +84,8 @@
             }
         };
 
-        PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+                PendingIntent.FLAG_MUTABLE);
         is.send(Activity.RESULT_CANCELED, finish, null);
         waitForResultOrThrow(BROADCAST_TIMEOUT);
         is.cancel();
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index 6186192..e0eb197 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -48,7 +48,8 @@
     public void setUp() {
         final Context context = InstrumentationRegistry.getContext();
         mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
-                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
+                new Intent().setPackage(context.getPackageName()),
+                PendingIntent.FLAG_MUTABLE /* flags */);
         mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
     }
 
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
new file mode 100644
index 0000000..7706d9a
--- /dev/null
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ReplacementSpan;
+import android.view.View;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InsertModeTransformationMethodTest {
+    private static View sView;
+    private static final String TEXT = "abc def";
+
+    @BeforeClass
+    public static void setupClass() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        sView = new View(context);
+    }
+
+    @Test
+    public void transformedText_charAt() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, false, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final CharSequence expected =
+                    TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+            assertCharSequence(transformedText, expected);
+        }
+    }
+
+    @Test
+    public void transformedText_charAt_singleLine() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, true, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final CharSequence expected =
+                    TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+            assertCharSequence(transformedText, expected);
+        }
+    }
+
+    @Test
+    public void transformedText_charAt_editing() {
+        transformedText_charAt_editing(false, "\n\n");
+    }
+
+    @Test
+    public void transformedText_charAt_singleLine_editing() {
+        transformedText_charAt_editing(true, "\uFFFD");
+    }
+
+    public void transformedText_charAt_editing(boolean singleLine, String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        assertCharSequence(transformedText,  "abc" + placeholder + " def");
+
+        // original text is "abcxx def" after insertion.
+        text.insert(3, "xx");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+        // original text is "abcxx vvdef" after insertion.
+        text.insert(6, "vv");
+        assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+        // original text is "abc vvdef" after deletion.
+        text.delete(3, 5);
+        assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+        // original text is "abc def" after deletion.
+        text.delete(4, 6);
+        assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+        // original text is "abdef" after deletion.
+        // the placeholder is now inserted at index 2, since the deletion covers the index 3.
+        text.delete(2, 4);
+        assertCharSequence(transformedText, "ab" + placeholder + "def");
+
+        // original text is "axxdef" after replace.
+        text.replace(1, 2, "xx");
+        assertCharSequence(transformedText, "axx" + placeholder + "def");
+
+        // original text is "axxvvf" after replace.
+        text.replace(3, 5, "vv");
+        assertCharSequence(transformedText, "axx" + placeholder + "vvf");
+
+        // original text is "abc def" after replace.
+        // the placeholder is inserted at index 6 after the insertion, since the replacement covers
+        // the index 3.
+        text.replace(1, 5, "bc de");
+        assertCharSequence(transformedText, "abc de" + placeholder + "f");
+    }
+
+    @Test
+    public void transformedText_subSequence() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, false, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final CharSequence expected =
+                    TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+            for (int start = 0; start < transformedText.length(); ++start) {
+                for (int end = start; end <= transformedText.length(); ++end) {
+                    assertCharSequence(transformedText.subSequence(start, end),
+                            expected.subSequence(start, end));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void transformedText_subSequence_singleLine() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, true, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final CharSequence expected =
+                    TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+            for (int start = 0; start < transformedText.length(); ++start) {
+                for (int end = start; end <= transformedText.length(); ++end) {
+                    assertCharSequence(transformedText.subSequence(start, end),
+                            expected.subSequence(start, end));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void transformedText_toString() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, false, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final String expected =
+                    TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+            assertThat(transformedText.toString()).isEqualTo(expected);
+        }
+    }
+
+    @Test
+    public void transformedText_toString_singleLine() {
+        for (int offset = 0; offset < TEXT.length(); ++offset) {
+            final InsertModeTransformationMethod transformationMethod =
+                    new InsertModeTransformationMethod(offset, true, null);
+            final CharSequence transformedText =
+                    transformationMethod.getTransformation(TEXT, sView);
+            final String expected =
+                    TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+            assertThat(transformedText.toString()).isEqualTo(expected);
+        }
+    }
+
+
+    @Test
+    public void transformedText_getSpans() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText "abc\n\n def", the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [2, 6)
+        // span3: [6, 7)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, false, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        // only span1 is in the range of [0, 2).
+        final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+        assertThat(spans0to2.length).isEqualTo(1);
+        assertThat(spans0to2[0]).isEqualTo(span1);
+
+        // span1 and span2 are in the range of [1, 6).
+        final TestSpan[] spans1to6 = transformedText.getSpans(1, 6, TestSpan.class);
+        assertThat(spans1to6.length).isEqualTo(2);
+        assertThat(spans1to6[0]).isEqualTo(span1);
+        assertThat(spans1to6[1]).isEqualTo(span2);
+
+        // only span2 is in the range of [4, 6).
+        final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class);
+        assertThat(spans4to6.length).isEqualTo(1);
+        assertThat(spans4to6[0]).isEqualTo(span2);
+
+        // span2 and span3 are in the range of [4, 7).
+        final TestSpan[] spans4to7 = transformedText.getSpans(4, 7, TestSpan.class);
+        assertThat(spans4to7.length).isEqualTo(2);
+        assertThat(spans4to7[0]).isEqualTo(span2);
+        assertThat(spans4to7[1]).isEqualTo(span3);
+
+        // only span3 is in the range of [6, 7).
+        final TestSpan[] spans6to7 = transformedText.getSpans(6, 7, TestSpan.class);
+        assertThat(spans6to7.length).isEqualTo(1);
+        assertThat(spans6to7[0]).isEqualTo(span3);
+
+        // there is no span in the range of [7, 9).
+        final TestSpan[] spans7to9 = transformedText.getSpans(7, 9, TestSpan.class);
+        assertThat(spans7to9.length).isEqualTo(0);
+    }
+
+    @Test
+    public void transformedText_getSpans_singleLine() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText, the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [2, 5)
+        // span3: [5, 6)
+        // There should also be a ReplacementSpan in the range [3, 4).
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, true, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        // only span1 is in the range of [0, 2).
+        final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+        assertThat(spans0to2.length).isEqualTo(1);
+        assertThat(spans0to2[0]).isEqualTo(span1);
+
+        // span1 and span2 are in the range of [1, 5).
+        final TestSpan[] spans1to4 = transformedText.getSpans(1, 4, TestSpan.class);
+        assertThat(spans1to4.length).isEqualTo(2);
+        assertThat(spans1to4[0]).isEqualTo(span1);
+        assertThat(spans1to4[1]).isEqualTo(span2);
+
+        // only span2 is in the range of [3, 5).
+        final TestSpan[] spans3to5 = transformedText.getSpans(3, 5, TestSpan.class);
+        assertThat(spans3to5.length).isEqualTo(1);
+        assertThat(spans3to5[0]).isEqualTo(span2);
+
+        // span2 and span3 are in the range of [3, 6).
+        final TestSpan[] spans3to6 = transformedText.getSpans(3, 6, TestSpan.class);
+        assertThat(spans3to6.length).isEqualTo(2);
+        assertThat(spans3to6[0]).isEqualTo(span2);
+        assertThat(spans3to6[1]).isEqualTo(span3);
+
+        // only span3 is in the range of [5, 6).
+        final TestSpan[] spans5to6 = transformedText.getSpans(5, 6, TestSpan.class);
+        assertThat(spans5to6.length).isEqualTo(1);
+        assertThat(spans5to6[0]).isEqualTo(span3);
+
+        // there is no span in the range of [6, 8)
+        final TestSpan[] spans6to8 = transformedText.getSpans(6, 8, TestSpan.class);
+        assertThat(spans6to8.length).isEqualTo(0);
+
+        // When it's singleLine, there should be a ReplacementSpan in the range [3, 4)
+        final ReplacementSpan[] replacementSpans3to4 =
+                transformedText.getSpans(3, 4, ReplacementSpan.class);
+        assertThat(replacementSpans3to4.length).isEqualTo(1);
+
+        final ReplacementSpan[] replacementSpans0to3 =
+                transformedText.getSpans(0, 3, ReplacementSpan.class);
+        assertThat(replacementSpans0to3.length).isEqualTo(0);
+
+        final ReplacementSpan[] replacementSpans4to8 =
+                transformedText.getSpans(4, 8, ReplacementSpan.class);
+        assertThat(replacementSpans4to8.length).isEqualTo(0);
+    }
+
+    @Test
+    public void transformedText_getSpanStartAndEnd() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText, the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [2, 6)
+        // span3: [6, 7)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, false, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+        assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+        assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+        assertThat(transformedText.getSpanEnd(span2)).isEqualTo(6);
+
+        assertThat(transformedText.getSpanStart(span3)).isEqualTo(6);
+        assertThat(transformedText.getSpanEnd(span3)).isEqualTo(7);
+    }
+
+    @Test
+    public void transformedText_getSpanStartAndEnd_singleLine() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText, the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [2, 5)
+        // span3: [5, 6)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, true, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+        assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+        assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+        assertThat(transformedText.getSpanEnd(span2)).isEqualTo(5);
+
+        assertThat(transformedText.getSpanStart(span3)).isEqualTo(5);
+        assertThat(transformedText.getSpanEnd(span3)).isEqualTo(6);
+
+        final ReplacementSpan[] replacementSpans =
+                transformedText.getSpans(0, 8, ReplacementSpan.class);
+        assertThat(transformedText.getSpanStart(replacementSpans[0])).isEqualTo(3);
+        assertThat(transformedText.getSpanEnd(replacementSpans[0])).isEqualTo(4);
+    }
+
+    @Test
+    public void transformedText_getSpanFlag() {
+        transformedText_getSpanFlag(false);
+    }
+
+    @Test
+    public void transformedText_getSpanFlag_singleLine() {
+        transformedText_getSpanFlag(true);
+    }
+
+    public void transformedText_getSpanFlag(boolean singleLine) {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+        text.setSpan(span1, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+
+        assertThat(transformedText.getSpanFlags(span1)).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        assertThat(transformedText.getSpanFlags(span2)).isEqualTo(Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+        assertThat(transformedText.getSpanFlags(span3)).isEqualTo(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+    }
+
+    @Test
+    public void transformedText_nextSpanTransition() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText, the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [1, 6)
+        // span3: [6, 7)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, false, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+        final int[] expectedTransitions = new int[] { 0, 1, 3, 6, 7 };
+        assertNextSpanTransition(transformedText, expectedTransitions, TestSpan.class);
+    }
+
+    @Test
+    public void transformedText_nextSpanTransition_singleLine() {
+        final SpannableString text = new SpannableString(TEXT);
+        final TestSpan span1 = new TestSpan();
+        final TestSpan span2 = new TestSpan();
+        final TestSpan span3 = new TestSpan();
+
+
+        text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+        text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // In the transformedText, the new ranges of the spans are:
+        // span1: [0, 3)
+        // span2: [1, 5)
+        // span3: [5, 6)
+        // there is also a ReplacementSpan at range [3, 4)
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, true, null);
+        final Spanned transformedText =
+                (Spanned) transformationMethod.getTransformation(text, sView);
+        final int[] expectedTransitions = new int[] { 0, 1, 3, 4, 5, 6 };
+        assertNextSpanTransition(transformedText, expectedTransitions, Object.class);
+    }
+
+    @Test
+    public void transformedText_originalToTransformed() {
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(2, false, null);
+        final OffsetMapping transformedText =
+                (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+        // "abc def" is transformed to "ab\n\nc def".
+        final int[] mappedCharacterOffsets = new int[] { 0, 1, 4, 5, 6, 7, 8 };
+        assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCharacterOffsets);
+
+        // "abc def" is transformed to "ab\n\nc def".
+        // the cursor before 'c' is mapped to index position before "\n\n".
+        final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 5, 6, 7, 8 };
+        assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+                mappedCursorOffsets);
+    }
+
+    @Test
+    public void transformedText_originalToTransformed_singleLine() {
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(2, true, null);
+        final OffsetMapping transformedText =
+                (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+        // "abc def" is transformed to "ab\uFFFDc def".
+        final int[] mappedCharacterOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+        assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCharacterOffsets);
+
+        // "abc def" is transformed to "ab\uFFFDc def".
+        // the cursor before 'c' is mapped to index position before "\uFFFD".
+        final int[] mappedCursorOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+        assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCursorOffsets);
+    }
+
+    @Test
+    public void transformedText_transformedToOriginal() {
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(2, false, null);
+        final OffsetMapping transformedText =
+                (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+        // "abc def" is transformed to "ab\n\nc def".
+        // the two '\n' characters have no corresponding character; map them to 'c'.
+        final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+        assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCharacterOffsets);
+
+        // offset 2 and 3 (cursor positions before the two '\n' characters) are mapped to index 2
+        // (cursor position before 'c' in the original text)
+        final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+        assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+                mappedCursorOffsets);
+    }
+
+    @Test
+    public void transformedText_transformedToOriginal_singleLine() {
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(2, true, null);
+        final OffsetMapping transformedText =
+                (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+        // "abc def" is transformed to "ab\uFFFDc def".
+        // '\uFFFD' has no corresponding character; map it to 'c'.
+        final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+        assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCharacterOffsets);
+
+        // offset 2 (cursor positions before '\uFFFD') is mapped to index 2 (cursor position before
+        // 'c' in the original text)
+        final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+        assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+                mappedCursorOffsets);
+    }
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_insertion() {
+        transformedText_getHighlightStartAndEnd_insertion(false, "\n\n");
+    }
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+        transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
+    }
+
+    public void transformedText_getHighlightStartAndEnd_insertion(boolean singleLine,
+            String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxx def" after insertion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 3.
+        // the highlight end now is 5 + placeholder.length(), including the newly inserted text.
+        text.insert(3, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "abcxxvv def" after insertion.
+        // the placeholder is now inserted at index 7.
+        // the highlight start is still 3.
+        // the highlight end now is 7 + placeholder.length(), including the newly inserted text.
+        text.insert(5, "vv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abzzcxxvv def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is 5, since the insertion happens before the highlight range.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(2, "zz");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abzzcxxvv iidef" after insertion.
+        // the placeholder is still inserted at index 9.
+        // the highlight start is still 5, since the insertion happens after the highlight range.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(10, "ii");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+    }
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_deletion() {
+        transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
+    }
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+        transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
+    }
+
+    public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+            String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abxxxxxx def" after deletion.
+        // the placeholder is now inserted at index 6.
+        // the highlight start is 2, since the deletion happens before the highlight range.
+        // the highlight end now is 8 + placeholder.length().
+        text.delete(2, 3);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+        // original text is "abxxx def" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens in the highlight range.
+        // the highlight end now is 5 + placeholder.length().
+        text.delete(2, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "abxxx d" after deletion.
+        // the placeholder is now inserted at index 5.
+        // the highlight start is still 2, since the deletion happens after the highlight range.
+        // the highlight end now is still 5 + placeholder.length().
+        text.delete(7, 9);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+        // original text is "af" after deletion.
+        // the placeholder is now inserted at index 1.
+        // the highlight start is 1, since the deletion covers highlight range.
+        // the highlight end is 1 + placeholder.length().
+        text.delete(1, 5);
+        assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
+    }
+
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_replace() {
+        transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
+    }
+
+    @Test
+    public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+        transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
+    }
+
+    public void transformedText_getHighlightStartAndEnd_replace(boolean singleLine,
+            String placeholder) {
+        final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+        final InsertModeTransformationMethod transformationMethod =
+                new InsertModeTransformationMethod(3, singleLine, null);
+        final InsertModeTransformationMethod.TransformedText transformedText =
+                (InsertModeTransformationMethod.TransformedText) transformationMethod
+                        .getTransformation(text, sView);
+        // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+        text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // note: the placeholder text is also highlighted.
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+        // original text is "abcxxxxxx def" after insertion.
+        // the placeholder is now inserted at index 9.
+        // the highlight start is still 3.
+        // the highlight end now is 9 + placeholder.length().
+        text.insert(3, "xxxxxx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+        // original text is "abvvxxxxxx def" after replace.
+        // the replacement happens before the highlight range; highlight range is offset by 1
+        // the placeholder is now inserted at index 10,
+        // the highlight start is 4.
+        // the highlight end is 10 + placeholder.length().
+        text.replace(2, 3, "vv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+        // original text is "abvvxxx def" after replace.
+        // the replacement happens in the highlight range; highlight end is offset by -3
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(5, 9, "x");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abvvxxxvvv" after replace.
+        // the replacement happens after the highlight range; highlight is not changed
+        // the placeholder is now inserted at index 7,
+        // the highlight start is still 4.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(7, 11, "vvv");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "abxxxxvvv" after replace.
+        // the replacement happens covers the highlight start; highlight start extends to the
+        // replacement start; highlight end is offset by -1
+        // the placeholder is now inserted at index 6,
+        // the highlight start is 2.
+        // the highlight end is 6 + placeholder.length().
+        text.replace(2, 5, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+        // original text is "abxxxxxvv" after replace.
+        // the replacement happens covers the highlight end; highlight end extends to the
+        // replacement end; highlight start stays the same
+        // the placeholder is now inserted at index 7,
+        // the highlight start is 2.
+        // the highlight end is 7 + placeholder.length().
+        text.replace(5, 7, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+        // original text is "axxv" after replace.
+        // the replacement happens covers the highlight range; highlight start is set to the
+        // replacement start; highlight end is set to the replacement end
+        // the placeholder is now inserted at index 3,
+        // the highlight start is 1.
+        // the highlight end is 3 + placeholder.length().
+        text.replace(1, 8, "xx");
+        assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+        assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+    }
+
+    private static  <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
+            Class<T> type) {
+        int currentTransition = 0;
+        for (int transition : transitions) {
+            assertThat(currentTransition).isEqualTo(transition);
+            currentTransition =
+                    spanned.nextSpanTransition(currentTransition, spanned.length(), type);
+        }
+
+        // Make sure there is no transition after the currentTransition.
+        assertThat(currentTransition).isEqualTo(spanned.length());
+    }
+
+    private static void assertCharSequence(CharSequence actual, CharSequence expected) {
+        assertThat(actual.length()).isEqualTo(expected.length());
+        for (int index = 0; index < actual.length(); ++index) {
+            assertThat(actual.charAt(index)).isEqualTo(expected.charAt(index));
+        }
+    }
+
+    private static void assertOriginalToTransformed(OffsetMapping transformedText, int strategy,
+            int[] expected) {
+        for (int offset = 0; offset < expected.length; ++offset) {
+            assertThat(transformedText.originalToTransformed(offset, strategy))
+                    .isEqualTo(expected[offset]);
+        }
+    }
+
+    private static void assertTransformedToOriginal(OffsetMapping transformedText, int strategy,
+            int[] expected) {
+        for (int offset = 0; offset < expected.length; ++offset) {
+            assertThat(transformedText.transformedToOriginal(offset, strategy))
+                    .isEqualTo(expected[offset]);
+        }
+    }
+
+    private static class TestSpan { }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
index 79aeaa3..90f7d06 100644
--- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
@@ -86,6 +87,14 @@
     }
 
     @Test
+    public void testInsertModeGesture() {
+        verifyEqualityAfterUnparcel(new InsertModeGesture.Builder()
+                .setInsertionPoint(new PointF(1, 1)).setFallbackText("")
+                .setCancellationSignal(new CancellationSignal())
+                .build());
+    }
+
+    @Test
     public void  testDeleteGestureGesture() {
         verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
                 .setGranularity(HandwritingGesture.GRANULARITY_WORD)
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index bbf9f3c..31c5a76 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -324,7 +324,9 @@
         RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
         nested.setOnClickPendingIntent(
                 R.id.text,
-                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE)
         );
 
         RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -341,7 +343,9 @@
         RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
         inner.setOnClickPendingIntent(
                 R.id.text,
-                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE)
         );
 
         RemoteViews listItem = new RemoteViews(inner, inner);
@@ -357,7 +361,9 @@
         RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
         inner.setOnClickPendingIntent(
                 R.id.text,
-                PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+                PendingIntent.getActivity(mContext, 0,
+                        new Intent().setPackage(mContext.getPackageName()),
+                        PendingIntent.FLAG_MUTABLE)
         );
 
         RemoteViews listItem = new RemoteViews(
@@ -559,7 +565,9 @@
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
         for (int i = 1; i < 10; i++) {
             PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                    new Intent("android.widget.RemoteViewsTest_" + i), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                    new Intent("android.widget.RemoteViewsTest_" + i)
+                            .setPackage(mContext.getPackageName()),
+                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
             views.setOnClickPendingIntent(i, pi);
         }
         try {
@@ -575,7 +583,8 @@
 
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                new Intent("test"), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                new Intent("test").setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
         views.setOnClickPendingIntent(1, pi);
         RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
 
@@ -606,8 +615,9 @@
     public void sharedElement_pendingIntent_notifyParent() throws Exception {
         RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                new Intent("android.widget.RemoteViewsTest_shared_element"),
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+                new Intent("android.widget.RemoteViewsTest_shared_element")
+                        .setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
         views.setOnClickResponse(R.id.image, RemoteViews.RemoteResponse.fromPendingIntent(pi)
                 .addSharedElement(0, "e0")
                 .addSharedElement(1, "e1")
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
index 3e640c1..bdf42ac 100644
--- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,6 +28,10 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
+
 import org.junit.Test;
 
 import java.util.List;
@@ -96,7 +101,8 @@
         Intent intent = new Intent();
 
         AbstractResolverComparator testComparator =
-                new AbstractResolverComparator(context, intent) {
+                new AbstractResolverComparator(context, intent,
+                        Lists.newArrayList(context.getUser())) {
 
             @Override
             int compare(ResolveInfo lhs, ResolveInfo rhs) {
@@ -109,7 +115,7 @@
             void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
 
             @Override
-            float getScore(ComponentName name) {
+            float getScore(TargetInfo targetInfo) {
                 return 0;
             }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index eead4ed..5469843 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -29,7 +29,6 @@
 import android.util.Pair;
 
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
 import com.android.internal.app.chooser.TargetInfo;
 import com.android.internal.logging.MetricsLogger;
@@ -71,13 +70,14 @@
     public int alternateProfileSetting;
     public Resources resources;
     public UserHandle workProfileUserHandle;
+    public UserHandle cloneProfileUserHandle;
+    public UserHandle tabOwnerUserHandleForLaunch;
     public boolean hasCrossProfileIntents;
     public boolean isQuietModeEnabled;
     public boolean isWorkProfileUserRunning;
     public boolean isWorkProfileUserUnlocked;
     public Integer myUserId;
     public QuietModeManager mQuietModeManager;
-    public MyUserIdProvider mMyUserIdProvider;
     public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
     public PackageManager packageManager;
 
@@ -98,6 +98,8 @@
         alternateProfileSetting = 0;
         resources = null;
         workProfileUserHandle = null;
+        cloneProfileUserHandle = null;
+        tabOwnerUserHandleForLaunch = null;
         hasCrossProfileIntents = true;
         isQuietModeEnabled = false;
         isWorkProfileUserRunning = true;
@@ -126,13 +128,6 @@
             }
         };
 
-        mMyUserIdProvider = new MyUserIdProvider() {
-            @Override
-            public int getMyUserId() {
-                return myUserId != null ? myUserId : UserHandle.myUserId();
-            }
-        };
-
         mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
         when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
                 .thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index d656678..c06df94 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -143,7 +143,8 @@
      * `Parameterized` runner.
      * --------
      */
-
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
     private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm;
     private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM =
             pm -> {
@@ -472,7 +473,7 @@
         List<ResolvedComponentInfo> infosToStack = new ArrayList<>();
         for (int i = 0; i < 4; i++) {
             ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i,
-                    UserHandle.USER_CURRENT);
+                    UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
             resolveInfo.activityInfo.applicationInfo.name = appName;
             resolveInfo.activityInfo.applicationInfo.packageName = packageName;
             resolveInfo.activityInfo.packageName = packageName;
@@ -544,13 +545,17 @@
             return true;
         };
         ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+        DisplayResolveInfo testDri =
+                activity.createTestDisplayResolveInfo(sendIntent, toChoose, "testLabel", "testInfo",
+                        sendIntent,/* resolveInfoPresentationGetter */ null);
         onView(withText(toChoose.activityInfo.name))
                 .perform(click());
         waitForIdle();
         verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
-                .updateChooserCounts(Mockito.anyString(), anyInt(), Mockito.anyString());
+                .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
+                        Mockito.anyString());
         verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
-                .updateModel(toChoose.activityInfo.getComponentName());
+                .updateModel(testDri);
         assertThat(activity.getIsSelected(), is(true));
     }
 
@@ -1329,7 +1334,11 @@
 
         final DisplayResolveInfo testDri =
                 activity.createTestDisplayResolveInfo(sendIntent,
-                ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent,
+                ResolverDataProvider.createResolveInfo(
+                        3, 0, PERSONAL_USER_HANDLE),
+                        "testLabel",
+                        "testInfo",
+                        sendIntent,
                 /* resolveInfoPresentationGetter */ null);
         final ChooserListAdapter adapter = activity.getAdapter();
 
@@ -1450,7 +1459,8 @@
         ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+                PERSONAL_USER_HANDLE);
 
         // Start activity
         final IChooserWrapper activity = (IChooserWrapper)
@@ -1528,7 +1538,8 @@
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
                 resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+                PERSONAL_USER_HANDLE);
 
         // Start activity
         final IChooserWrapper activity = (IChooserWrapper)
@@ -1606,7 +1617,8 @@
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
                 resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+                PERSONAL_USER_HANDLE);
 
         // Start activity
         final ChooserActivity activity =
@@ -1679,7 +1691,8 @@
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
                 resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+                PERSONAL_USER_HANDLE);
 
         // Start activity
         final ChooserActivity activity =
@@ -1791,7 +1804,8 @@
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
                 resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0,
+                PERSONAL_USER_HANDLE);
 
         // Start activity
         final IChooserWrapper activity = (IChooserWrapper)
@@ -2187,7 +2201,8 @@
         // Create direct share target
         List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
                 resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
-        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+                PERSONAL_USER_HANDLE);
 
         ChooserActivityOverrideData
                 .getInstance()
@@ -2721,7 +2736,7 @@
                 new Intent[] {new Intent("action.fake")});
         ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
         ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
-                UserHandle.USER_CURRENT);
+                UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
         when(
                 ChooserActivityOverrideData
                         .getInstance()
@@ -2886,6 +2901,53 @@
         assertEquals(3, wrapper.getWorkListAdapter().getCount());
     }
 
+    @Test
+    public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        ChooserActivityOverrideData.getInstance().cloneProfileUserHandle);
+        when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class)))
+                .thenReturn(new ArrayList<>(resolvedComponentInfos));
+        Intent sendIntent = createSendTextIntent();
+
+        final IChooserWrapper activity = (IChooserWrapper) mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest"));
+        waitForIdle();
+
+        assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        markCloneProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsForTest(3);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+                4);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendTextIntent();
+        sendIntent.setType(TEST_MIME_TYPE);
+
+
+        final IChooserWrapper activity = (IChooserWrapper)
+                mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test"));
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
+    }
+
     private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
         Intent chooserIntent = new Intent();
         chooserIntent.setAction(Intent.ACTION_CHOOSER);
@@ -2906,6 +2968,7 @@
         ri.activityInfo.packageName = "fake.package.name";
         ri.activityInfo.applicationInfo = new ApplicationInfo();
         ri.activityInfo.applicationInfo.packageName = "fake.package.name";
+        ri.userHandle = UserHandle.CURRENT;
         return ri;
     }
 
@@ -2967,7 +3030,23 @@
     private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+            int numberOfResults,
+            UserHandle resolvedForPersonalUser,
+            UserHandle resolvedForClonedUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < 1; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForPersonalUser));
+        }
+        for (int i = 1; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForClonedUser));
         }
         return infoList;
     }
@@ -2977,9 +3056,11 @@
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
             if (i == 0) {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+                        PERSONAL_USER_HANDLE));
             } else {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                        PERSONAL_USER_HANDLE));
             }
         }
         return infoList;
@@ -2991,9 +3072,11 @@
         for (int i = 0; i < numberOfResults; i++) {
             if (i == 0) {
                 infoList.add(
-                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                                PERSONAL_USER_HANDLE));
             } else {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                        PERSONAL_USER_HANDLE));
             }
         }
         return infoList;
@@ -3003,7 +3086,8 @@
             int numberOfResults, int userId) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                    PERSONAL_USER_HANDLE));
         }
         return infoList;
     }
@@ -3097,6 +3181,10 @@
         ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
     }
 
+    private void markCloneProfileUserAvailable() {
+        ChooserActivityOverrideData.getInstance().cloneProfileUserHandle = UserHandle.of(11);
+    }
+
     private void setupResolverControllers(
             List<ResolvedComponentInfo> personalResolvedComponentInfos,
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
index b6ea9dd..db69cf2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -49,8 +49,8 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.internal.R;
-import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 
 import junit.framework.AssertionFailedError;
 
@@ -93,7 +93,7 @@
     public void testBlocker() {
         setUpPersonalAndWorkComponentInfos();
         sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
-        sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+        sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
 
         launchActivity(mTestCase.getIsSendAction());
         switchToTab(mTestCase.getTab());
@@ -238,19 +238,21 @@
     }
 
     private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
-            int numberOfResults, int userId) {
+            int numberOfResults, int userId, UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
             infoList.add(
-                    ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+                    ResolverDataProvider
+                            .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
         }
         return infoList;
     }
 
-    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+            UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
         }
         return infoList;
     }
@@ -262,9 +264,9 @@
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3,
-                        /* userId */ WORK_USER_HANDLE.getIdentifier());
+                        /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(workProfileTargets);
+                createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 5dc0c8b..d7a8b3a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,10 +16,6 @@
 
 package com.android.internal.app;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
@@ -34,12 +30,12 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Pair;
 import android.util.Size;
 
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
 import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
 import com.android.internal.app.chooser.DisplayResolveInfo;
 import com.android.internal.app.chooser.TargetInfo;
@@ -132,14 +128,6 @@
     }
 
     @Override
-    protected MyUserIdProvider createMyUserIdProvider() {
-        if (sOverrides.mMyUserIdProvider != null) {
-            return sOverrides.mMyUserIdProvider;
-        }
-        return super.createMyUserIdProvider();
-    }
-
-    @Override
     protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
         if (sOverrides.mCrossProfileIntentsChecker != null) {
             return sOverrides.mCrossProfileIntentsChecker;
@@ -155,13 +143,15 @@
         return super.createQuietModeManager();
     }
 
+    // TODO: Remove this and override safelyStartActivityInternal() instead.
     @Override
-    public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
+    public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+            @Nullable Bundle options) {
         if (sOverrides.onSafelyStartCallback != null &&
                 sOverrides.onSafelyStartCallback.apply(cti)) {
             return;
         }
-        super.safelyStartActivity(cti);
+        super.safelyStartActivityAsUser(cti, user, options);
     }
 
     @Override
@@ -253,6 +243,14 @@
     }
 
     @Override
+    protected UserHandle getTabOwnerUserHandleForLaunch() {
+        if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+            return super.getTabOwnerUserHandleForLaunch();
+        }
+        return sOverrides.tabOwnerUserHandleForLaunch;
+    }
+
+    @Override
     public Context createContextAsUser(UserHandle user, int flags) {
         // return the current context as a work profile doesn't really exist in these tests
         return getApplicationContext();
diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
index fbbe57c..573135ffa 100644
--- a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
+++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
@@ -19,6 +19,8 @@
 import android.content.ComponentName;
 import android.content.pm.ResolveInfo;
 
+import com.android.internal.app.chooser.TargetInfo;
+
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -45,14 +47,15 @@
     }
 
     @Override
-    public float getScore(ComponentName name) {
+    public float getScore(TargetInfo targetInfo) {
         return 0.0f;  // Models are not required to provide numerical scores.
     }
 
     @Override
-    public void notifyOnTargetSelected(ComponentName componentName) {
+    public void notifyOnTargetSelected(TargetInfo targetInfo) {
         System.out.println(
-                "User selected " + componentName + " under model " + System.identityHashCode(this));
+                "User selected " + targetInfo.getResolvedComponentName() + " under model "
+                        + System.identityHashCode(this));
     }
 
     private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 92c05b0..b82bc16 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -79,6 +79,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class ResolverActivityTest {
+
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
     @Rule
     public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
             new ActivityTestRule<>(ResolverWrapperActivity.class, false,
@@ -92,7 +95,8 @@
     @Test
     public void twoOptionsAndUserSelectsOne() throws InterruptedException {
         Intent sendIntent = createSendImageIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -124,7 +128,8 @@
     @Test
     public void setMaxHeight() throws Exception {
         Intent sendIntent = createSendImageIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -169,7 +174,8 @@
     @Test
     public void setShowAtTopToTrue() throws Exception {
         Intent sendIntent = createSendImageIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -201,7 +207,8 @@
     @Test
     public void hasLastChosenActivity() throws Exception {
         Intent sendIntent = createSendImageIntent();
-        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+                PERSONAL_USER_HANDLE);
         ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -234,10 +241,12 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
-        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
         markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
 
         ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
         Intent sendIntent = createSendImageIntent();
@@ -255,7 +264,8 @@
         };
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
-                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
+                        PERSONAL_USER_HANDLE);
         // We pick the first one as there is another one in the work profile side
         onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
                 .perform(click());
@@ -272,7 +282,7 @@
 
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3);
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
         ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -298,7 +308,7 @@
 
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
-                createResolvedComponentsForTestWithOtherProfile(2);
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
 
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
@@ -317,7 +327,7 @@
         // chosen activity.
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3);
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
         ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -345,7 +355,7 @@
 
         // Make a stable copy of the components as the original list may be modified
         List<ResolvedComponentInfo> stableCopy =
-                createResolvedComponentsForTestWithOtherProfile(2);
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
 
         onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
                 .perform(click());
@@ -428,12 +438,14 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+                        PERSONAL_USER_HANDLE);
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos,
                 new ArrayList<>(workResolvedComponentInfos));
         Intent sendIntent = createSendImageIntent();
-        markWorkProfileUserAvailable();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
         waitForIdle();
@@ -448,11 +460,13 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
-        markWorkProfileUserAvailable();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
         waitForIdle();
@@ -467,11 +481,12 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+        markWorkProfileUserAvailable();
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
-        markWorkProfileUserAvailable();
 
         final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
         waitForIdle();
@@ -487,8 +502,10 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
 
@@ -507,8 +524,10 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         ResolveInfo[] chosen = new ResolveInfo[1];
@@ -539,8 +558,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(1);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
 
@@ -559,8 +579,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(1);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createOpenWebsiteIntent();
 
@@ -578,8 +599,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(1);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createOpenWebsiteIntent();
 
@@ -605,8 +627,10 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
-        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+                        PERSONAL_USER_HANDLE);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         ResolveInfo[] chosen = new ResolveInfo[1];
@@ -639,9 +663,11 @@
         markWorkProfileUserAvailable();
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(workProfileTargets);
+                createResolvedComponentsForTest(workProfileTargets,
+                        sOverrides.workProfileUserHandle);
         sOverrides.hasCrossProfileIntents = false;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
@@ -665,9 +691,11 @@
         markWorkProfileUserAvailable();
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+                createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(workProfileTargets);
+                createResolvedComponentsForTest(workProfileTargets,
+                        sOverrides.workProfileUserHandle);
         sOverrides.isQuietModeEnabled = true;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
@@ -690,9 +718,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(0);
+                createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         sendIntent.setType("TestType");
@@ -714,9 +742,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(0);
+                createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         sendIntent.setType("TestType");
@@ -739,9 +767,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(1);
+                createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(1);
+                createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
         // Personal profile only has a browser
         personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
@@ -758,9 +786,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(0);
+                createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(1);
+                createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         sendIntent.setType("TestType");
@@ -787,9 +815,9 @@
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         markWorkProfileUserAvailable();
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(3);
+                createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(0);
+                createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
         sendIntent.setType("TestType");
@@ -810,7 +838,8 @@
     public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
         ResolverActivity.ENABLE_TABBED_VIEW = false;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -835,7 +864,7 @@
     public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
         ResolverActivity.ENABLE_TABBED_VIEW = false;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTest(1);
+                createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -863,9 +892,11 @@
         markWorkProfileUserAvailable();
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
-                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+                createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+                        PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(workProfileTargets);
+                createResolvedComponentsForTest(workProfileTargets,
+                        sOverrides.workProfileUserHandle);
         sOverrides.hasCrossProfileIntents = false;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendImageIntent();
@@ -892,7 +923,7 @@
         // chosen activity.
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos =
-                createResolvedComponentsForTest(2);
+                createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE);
 
         when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
                 Mockito.anyBoolean(),
@@ -911,6 +942,193 @@
         assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
     }
 
+    @Test
+    public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+        List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+                sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+        assertThat(activity.getAdapter().getCount(), is(3));
+    }
+
+    @Test
+    public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        2,
+                PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+        waitForIdle();
+
+        assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+        assertThat(activity.getAdapter().getCount(), is(2));
+        assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+    }
+
+    @Test
+    public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception {
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+        Intent sendIntent = createSendImageIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getLastChosen())
+                .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+
+        // Confirm that the button bar is disabled by default
+        onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+        onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+
+        // Make a stable copy of the components as the original list may be modified
+        List<ResolvedComponentInfo> stableCopy =
+                createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
+
+        onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+                .perform(click());
+
+        onView(withId(R.id.button_once)).check(matches(isEnabled()));
+        onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+    }
+
+    @Test
+    public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser()
+            throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+        sOverrides.hasCrossProfileIntents = false;
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+        sOverrides.onSafelyStartInternalCallback = userHandle -> {
+            selectedActivityUserHandle[0] = userHandle;
+            return true;
+        };
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(first(allOf(withText(personalResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+    }
+
+    @Test
+    public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser()
+            throws Exception {
+        // enable the work tab feature flag
+        ResolverActivity.ENABLE_TABBED_VIEW = true;
+        markWorkProfileUserAvailable();
+        // enable cloneProfile
+        markCloneProfileUserAvailable();
+
+        List<ResolvedComponentInfo> personalResolvedComponentInfos =
+                createResolvedComponentsWithCloneProfileForTest(
+                        3,
+                        PERSONAL_USER_HANDLE,
+                        sOverrides.cloneProfileUserHandle);
+        List<ResolvedComponentInfo> workResolvedComponentInfos =
+                createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+        setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+        Intent sendIntent = createSendImageIntent();
+        sendIntent.setType("TestType");
+        final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+        sOverrides.onSafelyStartInternalCallback = userHandle -> {
+            selectedActivityUserHandle[0] = userHandle;
+            return true;
+        };
+
+        final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+        waitForIdle();
+        onView(withText(R.string.resolver_work_tab))
+                .perform(click());
+        waitForIdle();
+        onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+                .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+                .perform(click());
+        onView(withId(R.id.button_once))
+                .perform(click());
+        waitForIdle();
+
+        assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+    }
+
     private Intent createSendImageIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -926,36 +1144,55 @@
         return sendIntent;
     }
 
-    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+            UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+        }
+        return infoList;
+    }
+
+    private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+            int numberOfResults,
+            UserHandle resolvedForPersonalUser,
+            UserHandle resolvedForClonedUser) {
+        List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+        for (int i = 0; i < 1; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForPersonalUser));
+        }
+        for (int i = 1; i < numberOfResults; i++) {
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+                    resolvedForClonedUser));
         }
         return infoList;
     }
 
     private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
-            int numberOfResults) {
+            int numberOfResults, UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
             if (i == 0) {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+                        resolvedForUser));
             } else {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
             }
         }
         return infoList;
     }
 
     private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
-            int numberOfResults, int userId) {
+            int numberOfResults, int userId, UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
             if (i == 0) {
                 infoList.add(
-                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+                        ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+                                resolvedForUser));
             } else {
-                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+                infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
             }
         }
         return infoList;
@@ -969,6 +1206,10 @@
         ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
     }
 
+    private void markCloneProfileUserAvailable() {
+        ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+    }
+
     private void setupResolverControllers(
             List<ResolvedComponentInfo> personalResolvedComponentInfos,
             List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
index 4a79f5e..9d16d85 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
@@ -92,7 +92,7 @@
     public void testBlocker() {
         setUpPersonalAndWorkComponentInfos();
         sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
-        sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+        sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
 
         launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
         switchToTab(mTestCase.getTab());
@@ -230,19 +230,21 @@
     }
 
     private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
-            int numberOfResults, int userId) {
+            int numberOfResults, int userId, UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
             infoList.add(
-                    ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+                    ResolverDataProvider
+                            .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
         }
         return infoList;
     }
 
-    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+    private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+            UserHandle resolvedForUser) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
         }
         return infoList;
     }
@@ -254,9 +256,9 @@
         int workProfileTargets = 4;
         List<ResolvedComponentInfo> personalResolvedComponentInfos =
                 createResolvedComponentsForTestWithOtherProfile(3,
-                        /* userId */ WORK_USER_HANDLE.getIdentifier());
+                        /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
         List<ResolvedComponentInfo> workResolvedComponentInfos =
-                createResolvedComponentsForTest(workProfileTargets);
+                createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index d7db5f8..55318f3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -36,20 +36,24 @@
 
     static private int USER_SOMEONE_ELSE = 10;
 
-    static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
-        return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
-                createResolverIntent(i), createResolveInfo(i, UserHandle.USER_CURRENT));
-    }
-
-    static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) {
-        return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
-                createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
+    static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i,
+            UserHandle resolvedForUser) {
+        return new ResolverActivity.ResolvedComponentInfo(
+                createComponentName(i),
+                createResolverIntent(i),
+                createResolveInfo(i, UserHandle.USER_CURRENT, resolvedForUser));
     }
 
     static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
-            int userId) {
+            UserHandle resolvedForUser) {
         return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
-                createResolverIntent(i), createResolveInfo(i, userId));
+                createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE, resolvedForUser));
+    }
+
+    static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+            int userId, UserHandle resolvedForUser) {
+        return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
+                createResolverIntent(i), createResolveInfo(i, userId, resolvedForUser));
     }
 
     static ComponentName createComponentName(int i) {
@@ -57,10 +61,11 @@
         return new ComponentName("foo.bar." + name, name);
     }
 
-    static ResolveInfo createResolveInfo(int i, int userId) {
+    static ResolveInfo createResolveInfo(int i, int userId, UserHandle resolvedForUser) {
         final ResolveInfo resolveInfo = new ResolveInfo();
         resolveInfo.activityInfo = createActivityInfo(i);
         resolveInfo.targetUserId = userId;
+        resolveInfo.userHandle = resolvedForUser;
         return resolveInfo;
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 42593f6..8f6cee3 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -44,6 +44,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
@@ -75,6 +76,8 @@
 
     private ResolverListController mController;
     private UsageStatsManager mUsm;
+    private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+            .getInstrumentation().getTargetContext().getUser();
 
     @Before
     public void setUp() throws Exception {
@@ -83,6 +86,7 @@
         config.locale = Locale.getDefault();
         List<ResolveInfo> services = new ArrayList<>();
         mUsm = new UsageStatsManager(mMockContext, mMockService);
+        when(mMockContext.createContextAsUser(any(), anyInt())).thenReturn(mMockContext);
         when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
         when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
         when(mMockResources.getConfiguration()).thenReturn(config);
@@ -119,10 +123,11 @@
                 anyString(), anyInt(), anyString(), any(), anyString());
         when(mMockContext.getOpPackageName()).thenReturn(refererPackage);
         mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
-                refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+                refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+                UserHandle.SYSTEM);
         mController.sort(new ArrayList<ResolvedComponentInfo>());
         long beforeReport = getCount(mUsm, packageName, action, annotation);
-        mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
+        mController.updateChooserCounts(packageName, UserHandle.SYSTEM, action);
         long afterReport = getCount(mUsm, packageName, action, annotation);
         assertThat(afterReport, is(beforeReport + 1l));
     }
@@ -134,7 +139,8 @@
         String refererPackage = "test_referer_package";
         List<ResolvedComponentInfo> resolvedComponents = createResolvedComponentsForTest(10);
         mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
-                refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+                refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+                UserHandle.SYSTEM);
         List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
         mController.topK(topKList, 5);
         List<ResolvedComponentInfo> sortList = new ArrayList<>(topKList);
@@ -157,12 +163,13 @@
     public void getResolversForIntent_usesResultsFromPackageManager() {
         mockStats();
         List<ResolveInfo> infos = new ArrayList<>();
-        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+                PERSONAL_USER_HANDLE));
         when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
                 any(UserHandle.class))).thenReturn(infos);
         mController = new ResolverListController(mMockContext, mMockPackageManager,
                 createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
-                /* userHandle= */ UserHandle.SYSTEM);
+                /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
         List<Intent> intents = new ArrayList<>();
         intents.add(createActionMainIntent());
 
@@ -181,12 +188,13 @@
     public void getResolversForIntent_shouldGetOnlyDefaultActivitiesTrue_addsFlag() {
         mockStats();
         List<ResolveInfo> infos = new ArrayList<>();
-        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+                PERSONAL_USER_HANDLE));
         when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
                 any(UserHandle.class))).thenReturn(infos);
         mController = new ResolverListController(mMockContext, mMockPackageManager,
                 createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
-                /* userHandle= */ UserHandle.SYSTEM);
+                /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
         List<Intent> intents = new ArrayList<>();
         intents.add(createActionMainIntent());
 
@@ -205,12 +213,13 @@
     public void getResolversForIntent_shouldGetOnlyDefaultActivitiesFalse_doesNotAddFlag() {
         mockStats();
         List<ResolveInfo> infos = new ArrayList<>();
-        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+                PERSONAL_USER_HANDLE));
         when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
                 any(UserHandle.class))).thenReturn(infos);
         mController = new ResolverListController(mMockContext, mMockPackageManager,
                 createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
-                /* userHandle= */ UserHandle.SYSTEM);
+                /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
         List<Intent> intents = new ArrayList<>();
         intents.add(createActionMainIntent());
 
@@ -225,6 +234,32 @@
                 doesNotContainFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
     }
 
+
+    @Test
+    public void testResolveInfoWithNoUserHandle_isNotAddedToResults()
+            throws Exception {
+        List<ResolveInfo> infos = new ArrayList<>();
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+                PERSONAL_USER_HANDLE));
+        infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT, null));
+        when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+                any(UserHandle.class))).thenReturn(infos);
+        mController = new ResolverListController(mMockContext, mMockPackageManager,
+                createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+                /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
+        List<Intent> intents = new ArrayList<>();
+        intents.add(createActionMainIntent());
+
+        List<ResolverActivity.ResolvedComponentInfo> result = mController
+                .getResolversForIntent(
+                        /* shouldGetResolvedFilter= */ true,
+                        /* shouldGetActivityMetadata= */ true,
+                        /* shouldGetOnlyDefaultActivities= */ false,
+                        intents);
+
+        assertThat(result.size(), is(1));
+    }
+
     private int containsFlag(int flag) {
         return intThat(new FlagMatcher(flag, /* contains= */ true));
     }
@@ -308,7 +343,7 @@
     private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
         List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
         for (int i = 0; i < numberOfResults; i++) {
-            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+            infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
         }
         return infoList;
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index c778dfe..8f6f29d 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.Intent;
@@ -30,7 +31,6 @@
 import android.os.UserHandle;
 
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
 import com.android.internal.app.chooser.TargetInfo;
 
@@ -57,14 +57,6 @@
     }
 
     @Override
-    protected MyUserIdProvider createMyUserIdProvider() {
-        if (sOverrides.mMyUserIdProvider != null) {
-            return sOverrides.mMyUserIdProvider;
-        }
-        return super.createMyUserIdProvider();
-    }
-
-    @Override
     protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
         if (sOverrides.mCrossProfileIntentsChecker != null) {
             return sOverrides.mCrossProfileIntentsChecker;
@@ -103,13 +95,25 @@
         return super.isVoiceInteraction();
     }
 
+    // TODO: Remove this and override safelyStartActivityInternal() instead.
     @Override
-    public void safelyStartActivity(TargetInfo cti) {
+    public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+            @Nullable Bundle options) {
         if (sOverrides.onSafelyStartCallback != null &&
                 sOverrides.onSafelyStartCallback.apply(cti)) {
             return;
         }
-        super.safelyStartActivity(cti);
+        super.safelyStartActivityAsUser(cti, user, options);
+    }
+
+    @Override
+    public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+            @Nullable Bundle options) {
+        if (sOverrides.onSafelyStartInternalCallback != null
+                && sOverrides.onSafelyStartInternalCallback.apply(user)) {
+            return;
+        }
+        super.safelyStartActivityInternal(cti, user, options);
     }
 
     @Override
@@ -135,11 +139,29 @@
     }
 
     @Override
+    protected UserHandle getPersonalProfileUserHandle() {
+        return super.getPersonalProfileUserHandle();
+    }
+
+    @Override
     protected UserHandle getWorkProfileUserHandle() {
         return sOverrides.workProfileUserHandle;
     }
 
     @Override
+    protected UserHandle getCloneProfileUserHandle() {
+        return sOverrides.cloneProfileUserHandle;
+    }
+
+    @Override
+    protected UserHandle getTabOwnerUserHandleForLaunch() {
+        if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+            return super.getTabOwnerUserHandleForLaunch();
+        }
+        return sOverrides.tabOwnerUserHandleForLaunch;
+    }
+
+    @Override
     public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
         super.startActivityAsUser(intent, options, user);
     }
@@ -153,24 +175,29 @@
         @SuppressWarnings("Since15")
         public Function<PackageManager, PackageManager> createPackageManager;
         public Function<TargetInfo, Boolean> onSafelyStartCallback;
+        public Function<UserHandle, Boolean> onSafelyStartInternalCallback;
         public ResolverListController resolverListController;
         public ResolverListController workResolverListController;
         public Boolean isVoiceInteraction;
         public UserHandle workProfileUserHandle;
+        public UserHandle cloneProfileUserHandle;
+        public UserHandle tabOwnerUserHandleForLaunch;
         public Integer myUserId;
         public boolean hasCrossProfileIntents;
         public boolean isQuietModeEnabled;
         public QuietModeManager mQuietModeManager;
-        public MyUserIdProvider mMyUserIdProvider;
         public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
 
         public void reset() {
             onSafelyStartCallback = null;
+            onSafelyStartInternalCallback = null;
             isVoiceInteraction = null;
             createPackageManager = null;
             resolverListController = mock(ResolverListController.class);
             workResolverListController = mock(ResolverListController.class);
             workProfileUserHandle = null;
+            cloneProfileUserHandle = null;
+            tabOwnerUserHandleForLaunch = null;
             myUserId = null;
             hasCrossProfileIntents = true;
             isQuietModeEnabled = false;
@@ -197,13 +224,6 @@
                 }
             };
 
-            mMyUserIdProvider = new MyUserIdProvider() {
-                @Override
-                public int getMyUserId() {
-                    return myUserId != null ? myUserId : UserHandle.myUserId();
-                }
-            };
-
             mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
             when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
                     .thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
index e5000a4..b2914d8 100644
--- a/core/tests/notificationtests/src/android/app/NotificationStressTest.java
+++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
@@ -110,8 +110,9 @@
 
     private void sendNotification(int id, CharSequence text) {
         // Fill in arbitrary content
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                PendingIntent.FLAG_MUTABLE);
         CharSequence title = text + " " + id;
         CharSequence subtitle = String.valueOf(System.currentTimeMillis());
         // Create "typical" notification with random icon
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
index 37af99c..3c71e6e 100644
--- a/core/tests/screenshothelpertests/Android.bp
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -13,7 +13,7 @@
     srcs: [
         "src/**/*.java",
     ],
-    
+
     static_libs: [
         "frameworks-base-testutils",
         "androidx.test.runner",
@@ -21,6 +21,7 @@
         "androidx.test.ext.junit",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
+        "testng",
     ],
 
     libs: [
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 2719431..5c9894e 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.util;
 
 import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
 
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.os.Bundle;
+import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.WindowManager;
@@ -79,30 +82,48 @@
 
     @Test
     public void testFullscreenScreenshot() {
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+        mScreenshotHelper.takeScreenshot(
                 WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
     @Test
+    public void testFullscreenScreenshotRequest() {
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .build();
+        mScreenshotHelper.takeScreenshot(request, mHandler, null);
+    }
+
+    @Test
     public void testProvidedImageScreenshot() {
-        mScreenshotHelper.provideScreenshot(
-                new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
-                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
+        HardwareBuffer buffer = HardwareBuffer.create(
+                10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .setTopComponent(new ComponentName("", ""))
+                .setTaskId(1)
+                .setUserId(1)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(new Rect())
+                .setInsets(Insets.NONE)
+                .build();
+        mScreenshotHelper.takeScreenshot(request, mHandler, null);
     }
 
     @Test
     public void testScreenshotTimesOut() {
         long timeoutMs = 10;
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .build();
 
         CountDownLatch lock = new CountDownLatch(1);
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
-                WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
-                mHandler,
-                timeoutMs,
+        mScreenshotHelper.takeScreenshotInternal(request, mHandler,
                 uri -> {
                     assertNull(uri);
                     lock.countDown();
-                });
+                }, timeoutMs);
 
         try {
             // Add tolerance for delay to prevent flakes.
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
new file mode 100644
index 0000000..30540a5
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotRequestTest {
+    private final ComponentName mComponentName =
+            new ComponentName("android.test", "android.test.Component");
+
+    @Test
+    public void testSimpleScreenshot() {
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertNull("Top component was expected to be null", out.getTopComponent());
+        assertEquals(INVALID_TASK_ID, out.getTaskId());
+        assertEquals(USER_NULL, out.getUserId());
+        assertNull("Bitmap was expected to be null", out.getBitmap());
+        assertNull("Bounds were expected to be null", out.getBoundsInScreen());
+        assertEquals(Insets.NONE, out.getInsets());
+    }
+
+    @Test
+    public void testProvidedScreenshot() {
+        Bitmap bitmap = makeHardwareBitmap(50, 50);
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(mComponentName)
+                        .setTaskId(2)
+                        .setUserId(3)
+                        .setBitmap(bitmap)
+                        .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+                        .setInsets(Insets.of(2, 3, 4, 5))
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertEquals(mComponentName, out.getTopComponent());
+        assertEquals(2, out.getTaskId());
+        assertEquals(3, out.getUserId());
+        assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+        assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
+        assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+    }
+
+    @Test
+    public void testProvidedScreenshot_nullBitmap() {
+        ScreenshotRequest.Builder inBuilder =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(mComponentName)
+                        .setTaskId(2)
+                        .setUserId(3)
+                        .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+                        .setInsets(Insets.of(2, 3, 4, 5));
+
+        assertThrows(IllegalStateException.class, inBuilder::build);
+    }
+
+    @Test
+    public void testFullScreenshot_withBitmap() {
+        // A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
+        Bitmap bitmap = makeHardwareBitmap(50, 50);
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+                        .setBitmap(bitmap)
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertNull(out.getTopComponent());
+        assertEquals(INVALID_TASK_ID, out.getTaskId());
+        assertEquals(USER_NULL, out.getUserId());
+        assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+        assertNull("Bounds expected to be null", out.getBoundsInScreen());
+        assertEquals(Insets.NONE, out.getInsets());
+    }
+
+    private Bitmap makeHardwareBitmap(int width, int height) {
+        HardwareBuffer buffer = HardwareBuffer.create(
+                width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+        return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bc3af1d..8b7265e 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2599,6 +2599,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "273212558": {
+      "message": "    info=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "274773837": {
       "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL transit=%s Callers=%s",
       "level": "VERBOSE",
@@ -3937,6 +3943,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1621562070": {
+      "message": "    startWCT=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1628345525": {
       "message": "Now opening app %s",
       "level": "VERBOSE",
@@ -4345,6 +4357,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "2021079047": {
+      "message": "%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "2022422429": {
       "message": "createAnimationAdapter(): container=%s",
       "level": "DEBUG",
@@ -4551,6 +4569,9 @@
     "WM_DEBUG_WINDOW_TRANSITIONS": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_WINDOW_TRANSITIONS_MIN": {
+      "tag": "WindowManager"
+    },
     "WM_ERROR": {
       "tag": "WindowManager"
     },
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 5d38494..38f1e28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -31,11 +31,12 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.util.TypedValue;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.window.BackEvent;
@@ -43,9 +44,10 @@
 import android.window.BackProgressAnimator;
 import android.window.IOnBackInvokedCallback;
 
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
 /** Class that defines cross-activity animation. */
@@ -56,24 +58,40 @@
      */
     private static final float MIN_WINDOW_SCALE = 0.9f;
 
-    /**
-     * Minimum alpha of the closing/entering window.
-     */
-    private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
-
-    /**
-     * Progress value to fly out closing window and fly in entering window.
-     */
-    private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
-
-    /** Max window translation in the Y axis. */
-    private static final int WINDOW_MAX_DELTA_Y = 160;
-
-    /** Duration of fade in/out entering window. */
-    private static final int FADE_IN_DURATION = 100;
     /** Duration of post animation after gesture committed. */
     private static final int POST_ANIMATION_DURATION = 350;
-    private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+    private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+    private static final FloatProperty<CrossActivityAnimation> ENTER_PROGRESS_PROP =
+            new FloatProperty<>("enter-alpha") {
+                @Override
+                public void setValue(CrossActivityAnimation anim, float value) {
+                    anim.setEnteringProgress(value);
+                }
+
+                @Override
+                public Float get(CrossActivityAnimation object) {
+                    return object.getEnteringProgress();
+                }
+            };
+    private static final FloatProperty<CrossActivityAnimation> LEAVE_PROGRESS_PROP =
+            new FloatProperty<>("leave-alpha") {
+                @Override
+                public void setValue(CrossActivityAnimation anim, float value) {
+                    anim.setLeavingProgress(value);
+                }
+
+                @Override
+                public Float get(CrossActivityAnimation object) {
+                    return object.getLeavingProgress();
+                }
+            };
+    private static final float MIN_WINDOW_ALPHA = 0.01f;
+    private static final float WINDOW_X_SHIFT_DP = 96;
+    private static final int SCALE_FACTOR = 100;
+    // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
+    private static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
+    private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+    private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
 
     private final Rect mStartTaskRect = new Rect();
     private final float mCornerRadius;
@@ -84,12 +102,13 @@
     // The entering window properties.
     private final Rect mEnteringStartRect = new Rect();
     private final RectF mEnteringRect = new RectF();
+    private final SpringAnimation mEnteringProgressSpring;
+    private final SpringAnimation mLeavingProgressSpring;
+    // Max window x-shift in pixels.
+    private final float mWindowXShift;
 
-    private float mCurrentAlpha = 1.0f;
-
-    private float mEnteringMargin = 0;
-    private ValueAnimator mEnteringAnimator;
-    private boolean mEnteringWindowShow = false;
+    private float mEnteringProgress = 0f;
+    private float mLeavingProgress = 0f;
 
     private final PointF mInitialTouchPos = new PointF();
 
@@ -115,16 +134,44 @@
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
         mBackground = background;
+        mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+        mEnteringProgressSpring.setSpring(new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+        mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP);
+        mLeavingProgressSpring.setSpring(new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+        mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP,
+                context.getResources().getDisplayMetrics());
     }
 
+    /**
+     * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two.
+     * From https://en.wikipedia.org/wiki/Smoothstep
+     */
+    private static float smoothstep(float edge0, float edge1, float x) {
+        if (x < edge0) return 0;
+        if (x >= edge1) return 1;
+
+        x = (x - edge0) / (edge1 - edge0);
+        return x * x * (3 - 2 * x);
+    }
+
+    /**
+     * Linearly map x from range (a1, a2) to range (b1, b2).
+     */
+    private static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+        return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+    }
+
+    /**
+     * Linearly map a normalized value from (0, 1) to (min, max).
+     */
     private static float mapRange(float value, float min, float max) {
         return min + (value * (max - min));
     }
 
-    private float getInterpolatedProgress(float backProgress) {
-        return INTERPOLATOR.getInterpolation(backProgress);
-    }
-
     private void startBackAnimation() {
         if (mEnteringTarget == null || mClosingTarget == null) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
@@ -169,9 +216,6 @@
         mBackInProgress = false;
         mTransformMatrix.reset();
         mInitialTouchPos.set(0, 0);
-        mEnteringWindowShow = false;
-        mEnteringMargin = 0;
-        mEnteringAnimator = null;
 
         if (mFinishCallback != null) {
             try {
@@ -181,6 +225,10 @@
             }
             mFinishCallback = null;
         }
+        mEnteringProgressSpring.animateToFinalPosition(0);
+        mEnteringProgressSpring.skipToEnd();
+        mLeavingProgressSpring.animateToFinalPosition(0);
+        mLeavingProgressSpring.skipToEnd();
     }
 
     private void onGestureProgress(@NonNull BackEvent backEvent) {
@@ -190,84 +238,12 @@
         }
         mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
 
-        if (mEnteringTarget == null || mClosingTarget == null) {
-            return;
-        }
-
-        final float progress = getInterpolatedProgress(backEvent.getProgress());
-        final float touchY = mTouchPos.y;
-
-        final int width = mStartTaskRect.width();
-        final int height = mStartTaskRect.height();
-
-        final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
-
-        final float closingWidth = closingScale * width;
-        final float closingHeight = (float) height / width * closingWidth;
-
-        // Move the window along the X axis.
-        final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
-
-        // Move the window along the Y axis.
-        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
-        final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
-        final float closingTop = (height - closingHeight) * 0.5f + deltaY;
-        mClosingRect.set(
-                closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
-        mEnteringRect.set(mClosingRect);
-
-        // Switch closing/entering targets while reach to the threshold progress.
-        if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
-            return;
-        }
-
-        // Present windows and update the alpha.
-        mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
-        mClosingRect.offset(mEnteringMargin, 0);
-        mEnteringRect.offset(mEnteringMargin - width, 0);
-
-        applyTransform(
-                mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
-        applyTransform(
-                mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
-        mTransaction.apply();
-    }
-
-    private boolean showEnteringWindow(boolean show) {
-        if (mEnteringAnimator == null) {
-            mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
-            mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
-            mEnteringAnimator.addUpdateListener(animation -> {
-                float progress = animation.getAnimatedFraction();
-                final int width = mStartTaskRect.width();
-                mEnteringMargin = width * progress;
-                // We don't animate to 0 or the surface would become invisible and lose focus.
-                final float alpha = progress >= 0.5f ? 0.01f
-                        : mapRange(progress * 2, mCurrentAlpha, 0.01f);
-                mClosingRect.offset(mEnteringMargin, 0);
-                mEnteringRect.offset(mEnteringMargin - width, 0);
-
-                applyTransform(mClosingTarget.leash, mClosingRect, alpha);
-                applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
-                mTransaction.apply();
-            });
-        }
-
-        if (mEnteringAnimator.isRunning()) {
-            return true;
-        }
-
-        if (mEnteringWindowShow == show) {
-            return false;
-        }
-
-        mEnteringWindowShow = show;
-        if (show) {
-            mEnteringAnimator.start();
-        } else {
-            mEnteringAnimator.reverse();
-        }
-        return true;
+        float progress = backEvent.getProgress();
+        float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
+                ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+                : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+        mLeavingProgressSpring.animateToFinalPosition(springProgress);
+        mEnteringProgressSpring.animateToFinalPosition(springProgress);
     }
 
     private void onGestureCommitted() {
@@ -275,11 +251,9 @@
             finishAnimation();
             return;
         }
-
-        // End the fade in animation.
-        if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
-            mEnteringAnimator.cancel();
-        }
+        // End the fade animations
+        mLeavingProgressSpring.cancel();
+        mEnteringProgressSpring.cancel();
 
         // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
         // coordinate of the gesture driven phase.
@@ -309,12 +283,79 @@
         float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
         float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
         float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
-        float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+        float alpha = mapRange(progress, mEnteringProgress, 1.0f);
 
         mEnteringRect.set(left, top, left + width, top + height);
         applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
     }
 
+    private float getEnteringProgress() {
+        return mEnteringProgress * SCALE_FACTOR;
+    }
+
+    private void setEnteringProgress(float value) {
+        mEnteringProgress = value / SCALE_FACTOR;
+        if (mEnteringTarget != null && mEnteringTarget.leash != null) {
+            transformWithProgress(
+                    mEnteringProgress,
+                    Math.max(
+                            smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+                            MIN_WINDOW_ALPHA),  /* alpha */
+                    mEnteringTarget.leash,
+                    mEnteringRect,
+                    -mWindowXShift,
+                    0
+            );
+        }
+    }
+
+    private float getLeavingProgress() {
+        return mLeavingProgress * SCALE_FACTOR;
+    }
+
+    private void setLeavingProgress(float value) {
+        mLeavingProgress = value / SCALE_FACTOR;
+        if (mClosingTarget != null && mClosingTarget.leash != null) {
+            transformWithProgress(
+                    mLeavingProgress,
+                    Math.max(
+                            1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+                            MIN_WINDOW_ALPHA),
+                    mClosingTarget.leash,
+                    mClosingRect,
+                    0,
+                    mWindowXShift
+            );
+        }
+    }
+
+    private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
+            RectF targetRect, float deltaXMin, float deltaXMax) {
+        final float touchY = mTouchPos.y;
+
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress);
+        final float closingScale = MIN_WINDOW_SCALE
+                + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE);
+        final float closingWidth = closingScale * width;
+        final float closingHeight = (float) height / width * closingWidth;
+
+        // Move the window along the X axis.
+        float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+        closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
+
+        // Move the window along the Y axis.
+        final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        final float closingTop = (height - closingHeight) * 0.5f;
+        targetRect.set(
+                closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+
+        applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA));
+        mTransaction.apply();
+    }
+
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
@@ -330,10 +371,12 @@
         @Override
         public void onBackCancelled() {
             // End the fade in animation.
-            if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
-                mEnteringAnimator.cancel();
-            }
             mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+            mEnteringProgressSpring.cancel();
+            mLeavingProgressSpring.cancel();
+            // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+            mProgressAnimator.reset();
+            finishAnimation();
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 9edfffc..d0aef20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -384,7 +384,7 @@
                 @Nullable ImeTracker.Token statsToken) {
             final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
             if (imeSource == null || mImeSourceControl == null) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             final Rect newFrame = imeSource.getFrame();
@@ -407,8 +407,7 @@
             }
             if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
-                ImeTracker.forLogging().onCancelled(
-                        statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+                ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             boolean seek = false;
@@ -452,7 +451,7 @@
                 mTransactionPool.release(t);
             });
             mAnimation.setInterpolator(INTERPOLATOR);
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
             mAnimation.addListener(new AnimatorListenerAdapter() {
                 private boolean mCancelled = false;
                 @Nullable
@@ -475,7 +474,7 @@
                             : 1.f;
                     t.setAlpha(mImeSourceControl.getLeash(), alpha);
                     if (mAnimationDirection == DIRECTION_SHOW) {
-                        ImeTracker.forLogging().onProgress(mStatsToken,
+                        ImeTracker.get().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.show(mImeSourceControl.getLeash());
                     }
@@ -512,15 +511,15 @@
                     }
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
-                        ImeTracker.forLogging().onProgress(mStatsToken,
+                        ImeTracker.get().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.hide(mImeSourceControl.getLeash());
                         removeImeSurface();
-                        ImeTracker.forLogging().onHidden(mStatsToken);
+                        ImeTracker.get().onHidden(mStatsToken);
                     } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
-                        ImeTracker.forLogging().onShown(mStatsToken);
+                        ImeTracker.get().onShown(mStatsToken);
                     } else if (mCancelled) {
-                        ImeTracker.forLogging().onCancelled(mStatsToken,
+                        ImeTracker.get().onCancelled(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                     }
                     if (DEBUG_IME_VISIBILITY) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 9bdda14..8759301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -162,12 +162,10 @@
                 @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
-                ImeTracker.forLogging().onFailed(
-                        statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
-            ImeTracker.forLogging().onProgress(
-                    statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
                 listener.showInsets(types, fromIme, statsToken);
             }
@@ -177,12 +175,10 @@
                 @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
-                ImeTracker.forLogging().onFailed(
-                        statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
-            ImeTracker.forLogging().onProgress(
-                    statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
                 listener.hideInsets(types, fromIme, statsToken);
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 06df9568..4721741 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -46,7 +46,7 @@
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true">
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
         <uses-library android:name="android.test.runner"/>
 
         <service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 8a694f7..7aa40e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -70,6 +70,18 @@
 
     @Presubmit
     @Test
+    override fun pipAppWindowAlwaysVisible() {
+        // In gestural nav the pip will first move behind home and then above home. The visual
+        // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+        // But the internal states of activity don't need to follow that, such as a temporary
+        // visibility state can be changed quickly outside a transaction so the test doesn't
+        // detect that. Hence, skip the case to avoid restricting the internal implementation.
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.pipAppWindowAlwaysVisible()
+    }
+
+    @Presubmit
+    @Test
     override fun pipAppLayerAlwaysVisible() {
         if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
         else {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
new file mode 100644
index 0000000..e133443
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test minimizing a pip window via pinch in gesture. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+    override val transition: FlickerBuilder.() -> Unit
+        get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+
+    /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+    @Postsubmit
+    @Test
+    fun pipLayerAreaDecreases() {
+        flicker.assertLayers {
+            val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+            pipLayerList.zipWithNext { previous, current ->
+                current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+            }
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTest> {
+            return FlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index ce31aac..415c270 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,7 +18,7 @@
 
 import android.app.Instrumentation
 import android.content.Intent
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -80,7 +80,7 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun hasAtMostOnePipDismissOverlayWindow() {
         val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index c08ad69..70a1523 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.IwTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
@@ -136,7 +137,7 @@
     }
 
     /** {@inheritDoc} */
-    @Presubmit
+    @FlakyTest(bugId = 264241018)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6affc6a..b0896da 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -138,7 +138,9 @@
     skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
 
     SkAndroidFrameworkTraceUtil::setEnableTracing(
-            base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
+            base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+    SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
+            base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 96a5176..ed7175e 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,9 +143,32 @@
 #define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
 
 /**
- * Allows to record Skia drawing commands with systrace.
+ * Allows broad recording of Skia drawing commands.
+ *
+ * If disabled, a very minimal set of trace events *may* be recorded.
+ * If enabled, a much broader set of trace events *may* be recorded.
+ *
+ * In either case, trace events are only recorded if an appropriately configured tracing session is
+ * active.
+ *
+ * Use debug.hwui.skia_use_perfetto_track_events to determine if ATrace (default) or Perfetto is
+ * used as the tracing backend.
  */
-#define PROPERTY_SKIA_ATRACE_ENABLED "debug.hwui.skia_atrace_enabled"
+#define PROPERTY_SKIA_TRACING_ENABLED "debug.hwui.skia_tracing_enabled"
+
+/**
+ * Switches Skia's tracing to use Perfetto's Track Event system instead of ATrace.
+ *
+ * If disabled, ATrace will be used by default, which will record trace events from any of Skia's
+ * tracing categories if overall system tracing is active and the "gfx" and "view" ATrace categories
+ * are enabled.
+ *
+ * If enabled, then Perfetto's Track Event system will be used instead, which will only record if an
+ * active Perfetto tracing session is targeting the correct apps and Skia tracing categories with
+ * the Track Event data source enabled. This approach may be used to selectively filter out
+ * undesired Skia tracing categories, and events will contain more data fields.
+ */
+#define PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS "debug.hwui.skia_use_perfetto_track_events"
 
 /**
  * Defines how many frames in a sequence to capture.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e1030b0..cb385d4 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -34,7 +34,6 @@
 #include "SkImageFilter.h"
 #include "SkImageInfo.h"
 #include "SkLatticeIter.h"
-#include "SkMath.h"
 #include "SkPaint.h"
 #include "SkPicture.h"
 #include "SkRRect.h"
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 47bd0b9..b58f517 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -16,7 +16,6 @@
 
 #include "SkiaInterpolator.h"
 
-#include "include/core/SkMath.h"
 #include "include/core/SkScalar.h"
 #include "include/core/SkTypes.h"
 #include "include/private/SkFixed.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index c98b87a..2f3e9bf 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -14,7 +14,6 @@
 #include "SkColorSpace.h"
 #include "SkEncodedImageFormat.h"
 #include "SkImageInfo.h"
-#include "SkMath.h"
 #include "SkPaint.h"
 #include "SkPixelRef.h"
 #include "SkRect.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index c835849..ae2e974 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -14,7 +14,6 @@
 #include "SkColorSpace.h"
 #include "SkFontMetrics.h"
 #include "SkImageInfo.h"
-#include "SkMath.h"
 #include "SkPixelRef.h"
 #include "SkPoint.h"
 #include "SkRect.h"
diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
index 18cfce5..c019a8c 100644
--- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
+++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
@@ -83,7 +83,9 @@
         super.onCreate(savedInstanceState);
 
         mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
-        mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        mPermissionIntent = PendingIntent.getBroadcast(this, 0,
+                new Intent(ACTION_USB_PERMISSION).setPackage(this.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
         registerReceiver(mUsbReceiver, filter);
 
diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java
index ce73be1..3558dd5 100644
--- a/location/java/android/location/GnssAntennaInfo.java
+++ b/location/java/android/location/GnssAntennaInfo.java
@@ -347,8 +347,8 @@
         @Override
         public String toString() {
             return "SphericalCorrections{"
-                    + "Corrections=" + Arrays.toString(mCorrections)
-                    + ", CorrectionUncertainties=" + Arrays.toString(mCorrectionUncertainties)
+                    + "Corrections=" + Arrays.deepToString(mCorrections)
+                    + ", CorrectionUncertainties=" + Arrays.deepToString(mCorrectionUncertainties)
                     + ", DeltaTheta=" + getDeltaTheta()
                     + ", DeltaPhi=" + getDeltaPhi()
                     + '}';
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 24c5b41..3fbada7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -106,7 +106,6 @@
     private Context mOriginalContext;
     private Context mApplicationContext;
     private @Nullable VirtualDeviceManager mVirtualDeviceManager; // Lazy initialized.
-    private long mVolumeKeyUpTime;
     private static final String TAG = "AudioManager";
     private static final boolean DEBUG = false;
     private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -911,7 +910,7 @@
         int keyCode = event.getKeyCode();
         if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
                 && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
-                && mVolumeKeyUpTime + AudioSystem.PLAY_SOUND_DELAY > SystemClock.uptimeMillis()) {
+                && AudioSystem.PLAY_SOUND_DELAY > SystemClock.uptimeMillis()) {
             /*
              * The user has hit another key during the delay (e.g., 300ms)
              * since the last volume key up, so cancel any sounds.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index fa74a9f..a7959c5 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1515,6 +1515,16 @@
         }
 
         /**
+         * Returns the current {@link RoutingSessionInfo} associated to this controller.
+         */
+        @NonNull
+        public RoutingSessionInfo getRoutingSessionInfo() {
+            synchronized (mControllerLock) {
+                return mSessionInfo;
+            }
+        }
+
+        /**
          * Gets the information about how volume is handled on the session.
          *
          * <p>Please note that you may not control the volume of the session even when you can
@@ -1873,13 +1883,6 @@
             return result.toString();
         }
 
-        @NonNull
-        RoutingSessionInfo getRoutingSessionInfo() {
-            synchronized (mControllerLock) {
-                return mSessionInfo;
-            }
-        }
-
         void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
             synchronized (mControllerLock) {
                 mSessionInfo = info;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 1e270b1..40a4858 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -827,16 +827,18 @@
             ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
         }
 
-        final String mimeType = resolver.getType(ringtoneUri);
-        if (mimeType == null) {
-            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
-                    + " ignored: failure to find mimeType (no access from this context?)");
-            return;
-        }
-        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
-            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
-                    + " ignored: associated mimeType:" + mimeType + " is not an audio type");
-            return;
+        if (ringtoneUri != null) {
+            final String mimeType = resolver.getType(ringtoneUri);
+            if (mimeType == null) {
+                Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                        + " ignored: failure to find mimeType (no access from this context?)");
+                return;
+            }
+            if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
+                Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                        + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+                return;
+            }
         }
 
         Settings.System.putStringForUser(resolver, setting,
diff --git a/omapi/OWNERS b/omapi/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/OWNERS
+++ b/omapi/OWNERS
@@ -1,5 +1,6 @@
 # Bug component: 456592
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
 jackcwyu@google.com
diff --git a/omapi/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/java/android/se/OWNERS
+++ b/omapi/java/android/se/OWNERS
@@ -1,5 +1,6 @@
 # Bug component: 456592
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
 jackcwyu@google.com
diff --git a/omapi/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
index 5682fd3..1dce1e0 100644
--- a/omapi/java/android/se/omapi/OWNERS
+++ b/omapi/java/android/se/omapi/OWNERS
@@ -1,5 +1,6 @@
 # Bug component: 456592
 
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
 alisher@google.com
 jackcwyu@google.com
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 3f86aba..8f9730a 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,8 +34,7 @@
         android:label="@string/app_name"
         android:directBootAware="true"
         android:usesCleartextTraffic="true"
-        android:icon="@mipmap/ic_launcher_android"
-        android:debuggable="true">
+        android:icon="@mipmap/ic_launcher_android">
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
             android:exported="true">
             <intent-filter>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 81505e1..d6909719 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -11,13 +11,34 @@
   <string name="string_continue">Continue</string>
   <!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
   <string name="string_more_options">More options</string>
+  <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
+  <string name="string_learn_more">Learn more</string>
   <!-- This string introduces passkeys to the users for the first time they use this method. Tip: to avoid gendered language patterns, this header could be translated as if the original string were "More safety with passkeys". [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_title">Safer with passkeys</string>
-  <!-- These strings highlight passkey benefits. [CHAR LIMIT=200] -->
+  <!-- This string highlight passkey benefits related with the password. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_body_password">With passkeys, you don’t need to create or remember complex passwords</string>
+  <!-- This string highlight passkey benefits related with encrypted. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</string>
+  <!-- This string highlight passkey benefits related with signing with other devices. [CHAR LIMIT=200] -->
   <string name="passkey_creation_intro_body_device">They are saved to a password manager, so you can sign in on other devices</string>
-
+  <!-- This string introduces passkeys in more detail to the users for the first time they use this method. [CHAR LIMIT=200] -->
+  <string name="more_about_passkeys_title">More about passkeys</string>
+  <!-- Title for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=80] -->
+  <string name="passwordless_technology_title">Passwordless technology</string>
+  <!-- Detail for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=500] -->
+  <string name="passwordless_technology_detail">Passkeys allow you to sign in without relying on passwords. You just need to use your fingerprint, face recognition, PIN, or swipe pattern to verify your identity and create a passkey.</string>
+  <!-- Title for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=80] -->
+  <string name="public_key_cryptography_title">Public key cryptography</string>
+  <!-- Detail for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=500] -->
+  <string name="public_key_cryptography_detail">Based on FIDO Alliance (which includes Google, Apple, Microsoft, and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is  shared with the app or website server. With corresponding keys, you can instantly register and sign in.</string>
+  <!-- Title for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=80] -->
+  <string name="improved_account_security_title">Improved account security</string>
+  <!-- Detail for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=500] -->
+  <string name="improved_account_security_detail">Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder.</string>
+  <!-- Title for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=80] -->
+  <string name="seamless_transition_title">Seamless transition</string>
+  <!-- Detail for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=500] -->
+  <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys.</string>
   <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
   <string name="choose_provider_title">Choose where to save your <xliff:g id="createTypes" example="passkeys">%1$s</xliff:g></string>
   <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 7d43364..a1392ba 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -414,7 +414,7 @@
         credentialData,
         // TODO: populate with actual data
         /*candidateQueryData=*/ Bundle(),
-        /*requireSystemProvider=*/ false
+        /*isSystemProviderRequired=*/ false
       ),
       "com.google.android.youtube"
     )
@@ -429,7 +429,7 @@
         data,
         // TODO: populate with actual data
         /*candidateQueryData=*/ Bundle(),
-        /*requireSystemProvider=*/ false
+        /*isSystemProviderRequired=*/ false
       ),
       "com.google.android.youtube"
     )
@@ -443,7 +443,7 @@
         "other-sign-ins",
         data,
         /*candidateQueryData=*/ Bundle(),
-        /*requireSystemProvider=*/ false
+        /*isSystemProviderRequired=*/ false
       ),
       "com.google.android.youtube"
     )
@@ -457,7 +457,7 @@
       )
         .addGetCredentialOption(
           GetCredentialOption(
-            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
+            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*isSystemProviderRequired=*/ false)
         )
         .build(),
       "com.google.android.youtube"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 09f9b5e..d9e4dc8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -269,7 +269,7 @@
       val createCredentialRequest = requestInfo.createCredentialRequest
       val createCredentialRequestJetpack = createCredentialRequest?.let {
         CreateCredentialRequest.createFrom(
-                it.type, it.credentialData, it.candidateQueryData, it.requireSystemProvider()
+                it.type, it.credentialData, it.candidateQueryData, it.isSystemProviderRequired()
         )
       }
       when (createCredentialRequestJetpack) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 498f0a1..03d7de5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -70,7 +70,7 @@
                 when (uiState.currentScreenState) {
                     CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
                         onConfirm = viewModel::onConfirmIntro,
-                        onCancel = viewModel::onCancel,
+                        onLearnMore = viewModel::onLearnMore,
                     )
                     CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
@@ -115,7 +115,10 @@
                         activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
                         onOptionSelected = viewModel::onEntrySelected,
                         onConfirm = viewModel::onConfirmEntrySelected,
-                        onCancel = viewModel::onCancel,
+                    )
+                    CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
+                        onBackPasskeyIntroButtonSelected =
+                        viewModel::onBackPasskeyIntroButtonSelected,
                     )
                 }
             } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
@@ -136,7 +139,7 @@
 @Composable
 fun ConfirmationCard(
     onConfirm: () -> Unit,
-    onCancel: () -> Unit,
+    onLearnMore: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -223,8 +226,8 @@
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
                 ActionButton(
-                    stringResource(R.string.string_cancel),
-                    onClick = onCancel
+                    stringResource(R.string.string_learn_more),
+                    onClick = onLearnMore
                 )
                 ConfirmButton(
                     stringResource(R.string.string_continue),
@@ -396,8 +399,7 @@
                         )
                     }
                 },
-                colors = TopAppBarDefaults.smallTopAppBarColors
-                    (containerColor = Color.Transparent),
+                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
                 modifier = Modifier.padding(top = 12.dp)
             )
             Divider(
@@ -634,7 +636,6 @@
     activeRemoteEntry: EntryInfo,
     onOptionSelected: (EntryInfo) -> Unit,
     onConfirm: () -> Unit,
-    onCancel: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -673,13 +674,9 @@
                 color = Color.Transparent
             )
             Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
+                horizontalArrangement = Arrangement.End,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                ActionButton(
-                    stringResource(R.string.string_cancel),
-                    onClick = onCancel
-                )
                 ConfirmButton(
                     stringResource(R.string.string_continue),
                     onClick = onConfirm
@@ -696,6 +693,92 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
+fun MoreAboutPasskeysIntroCard(
+    onBackPasskeyIntroButtonSelected: () -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            TopAppBar(
+                title = {
+                    TextOnSurface(
+                        text =
+                        stringResource(
+                            R.string.more_about_passkeys_title),
+                        style = MaterialTheme.typography.titleMedium,
+                    )
+                },
+                navigationIcon = {
+                    IconButton(
+                        onClick = onBackPasskeyIntroButtonSelected
+                    ) {
+                        Icon(
+                            Icons.Filled.ArrowBack,
+                            stringResource(R.string.accessibility_back_arrow_button)
+                        )
+                    }
+                },
+                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
+                modifier = Modifier.padding(top = 12.dp)
+            )
+            Column(
+                modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp)
+            ) {
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.passwordless_technology_title),
+                    style = MaterialTheme.typography.titleLarge,
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passwordless_technology_detail),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+                Divider(
+                    thickness = 24.dp,
+                    color = Color.Transparent
+                )
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.public_key_cryptography_title),
+                    style = MaterialTheme.typography.titleLarge,
+                )
+                TextSecondary(
+                    text = stringResource(R.string.public_key_cryptography_detail),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+                Divider(
+                    thickness = 24.dp,
+                    color = Color.Transparent
+                )
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.improved_account_security_title),
+                    style = MaterialTheme.typography.titleLarge,
+                )
+                TextSecondary(
+                    text = stringResource(R.string.improved_account_security_detail),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+                Divider(
+                    thickness = 24.dp,
+                    color = Color.Transparent
+                )
+                TextOnSurfaceVariant(
+                    text = stringResource(R.string.seamless_transition_title),
+                    style = MaterialTheme.typography.titleLarge,
+                )
+                TextSecondary(
+                    text = stringResource(R.string.seamless_transition_detail),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 24.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
 fun PrimaryCreateOptionRow(
     requestDisplayInfo: RequestDisplayInfo,
     entryInfo: EntryInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index ac84503..7a04a8e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -122,6 +122,12 @@
     )
   }
 
+  fun onBackPasskeyIntroButtonSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+    )
+  }
+
   fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
     uiState = uiState.copy(
       currentScreenState = if (
@@ -150,6 +156,12 @@
     dialogResult.tryEmit(DialogResult(ResultState.NORMAL_CANCELED))
   }
 
+  fun onLearnMore() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO,
+    )
+  }
+
   fun onChangeDefaultSelected() {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 97477a7..957488f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -91,6 +91,7 @@
 /** The name of the current screen. */
 enum class CreateScreenState {
   PASSKEY_INTRO,
+  MORE_ABOUT_PASSKEYS_INTRO,
   PROVIDER_SELECTION,
   CREATION_OPTION_SELECTION,
   MORE_OPTIONS_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 03f39e1..1cc1ac5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -63,6 +63,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
 import com.android.credentialmanager.common.ui.ActionButton
+import com.android.credentialmanager.common.ui.ConfirmButton
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TextOnSurface
 import com.android.credentialmanager.common.ui.TextSecondary
@@ -95,7 +96,10 @@
                         PrimarySelectionCard(
                             requestDisplayInfo = uiState.requestDisplayInfo,
                             providerDisplayInfo = uiState.providerDisplayInfo,
+                            providerInfoList = uiState.providerInfoList,
+                            activeEntry = uiState.activeEntry,
                             onEntrySelected = viewModel::onEntrySelected,
+                            onConfirm = viewModel::onConfirmEntrySelected,
                             onMoreOptionSelected = viewModel::onMoreOptionSelected,
                         )
                     } else {
@@ -133,7 +137,10 @@
 fun PrimarySelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
     providerDisplayInfo: ProviderDisplayInfo,
+    providerInfoList: List<ProviderInfo>,
+    activeEntry: EntryInfo?,
     onEntrySelected: (EntryInfo) -> Unit,
+    onConfirm: () -> Unit,
     onMoreOptionSelected: () -> Unit,
 ) {
     val sortedUserNameToCredentialEntryList =
@@ -217,13 +224,33 @@
                 thickness = 24.dp,
                 color = Color.Transparent
             )
+            var totalEntriesCount = sortedUserNameToCredentialEntryList
+                .flatMap{ it.sortedCredentialEntryList}.size + authenticationEntryList
+                .size + providerInfoList.flatMap { it.actionEntryList }.size
+            if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
+            // Row horizontalArrangement differs on only one actionButton(should place on most
+            // left)/only one confirmButton(should place on most right)/two buttons exist the same
+            // time(should be one on the left, one on the right)
             Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
+                horizontalArrangement =
+                if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End
+                else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start
+                else Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                ActionButton(
-                    stringResource(R.string.get_dialog_use_saved_passkey_for),
-                    onMoreOptionSelected)
+                if (totalEntriesCount > 1) {
+                    ActionButton(
+                        stringResource(R.string.get_dialog_use_saved_passkey_for),
+                        onMoreOptionSelected
+                    )
+                }
+                // Only one sign-in options exist
+                if (activeEntry != null) {
+                    ConfirmButton(
+                        stringResource(R.string.string_continue),
+                        onClick = onConfirm
+                    )
+                }
             }
             Divider(
                 thickness = 18.dp,
@@ -297,6 +324,12 @@
                             )
                         }
                     }
+                    item {
+                        Divider(
+                            thickness = 8.dp,
+                            color = Color.Transparent,
+                        )
+                    }
                     // From another device
                     val remoteEntry = providerDisplayInfo.remoteEntry
                     if (remoteEntry != null) {
@@ -307,6 +340,13 @@
                             )
                         }
                     }
+                    item {
+                        Divider(
+                            thickness = 1.dp,
+                            color = Color.LightGray,
+                            modifier = Modifier.padding(top = 16.dp)
+                        )
+                    }
                     // Manage sign-ins (action chips)
                     item {
                         ActionChips(
@@ -335,7 +375,7 @@
 
     TextSecondary(
         text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
-        style = MaterialTheme.typography.labelLarge,
+        style = MaterialTheme.typography.titleLarge,
         modifier = Modifier.padding(vertical = 8.dp)
     )
     // TODO: tweak padding.
@@ -343,7 +383,7 @@
         modifier = Modifier.fillMaxWidth().wrapContentHeight(),
         shape = MaterialTheme.shapes.medium,
     ) {
-        Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+        Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
             actionChips.forEach {
                 ActionEntryRow(it, onEntrySelected)
             }
@@ -358,7 +398,7 @@
 ) {
     TextSecondary(
         text = stringResource(R.string.get_dialog_heading_from_another_device),
-        style = MaterialTheme.typography.labelLarge,
+        style = MaterialTheme.typography.titleLarge,
         modifier = Modifier.padding(vertical = 8.dp)
     )
     ContainerCard(
@@ -376,7 +416,7 @@
                         painter = painterResource(R.drawable.ic_other_devices),
                         contentDescription = null,
                         tint = Color.Unspecified,
-                        modifier = Modifier.padding(start = 18.dp)
+                        modifier = Modifier.padding(start = 16.dp)
                     )
                 },
                 label = {
@@ -427,7 +467,7 @@
         text = stringResource(
             R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
         ),
-        style = MaterialTheme.typography.labelLarge,
+        style = MaterialTheme.typography.titleLarge,
         modifier = Modifier.padding(vertical = 8.dp)
     )
     ContainerCard(
@@ -554,7 +594,7 @@
     TransparentBackgroundEntry(
         icon = {
             Image(
-                modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                modifier = Modifier.padding(start = 10.dp).size(24.dp),
                 bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
                 // TODO: add description.
                 contentDescription = ""
@@ -565,13 +605,13 @@
                 TextOnSurfaceVariant(
                     text = actionEntryInfo.title,
                     style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(start = 5.dp),
+                    modifier = Modifier.padding(start = 8.dp),
                 )
                 if (actionEntryInfo.subTitle != null) {
                     TextSecondary(
                         text = actionEntryInfo.subTitle,
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(start = 5.dp),
+                        modifier = Modifier.padding(start = 8.dp),
                     )
                 }
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 6f0f76b..9859c89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -41,6 +41,7 @@
   val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
   val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
   val selectedEntry: EntryInfo? = null,
+  val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
   val hidden: Boolean = false,
   val providerActivityPending: Boolean = false,
   val isNoAccount: Boolean = false,
@@ -73,6 +74,17 @@
     }
   }
 
+  fun onConfirmEntrySelected() {
+    val activeEntry = uiState.activeEntry
+    if (activeEntry != null) {
+      onEntrySelected(activeEntry)
+    } else {
+      Log.w("Account Selector",
+        "Illegal state: confirm is pressed but activeEntry isn't set.")
+      dialogResult.tryEmit(DialogResult(ResultState.COMPLETE))
+    }
+  }
+
   fun launchProviderUi(
     launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
   ) {
@@ -198,6 +210,26 @@
   )
 }
 
+private fun toActiveEntry(
+  providerDisplayInfo: ProviderDisplayInfo,
+): EntryInfo? {
+  val sortedUserNameToCredentialEntryList =
+    providerDisplayInfo.sortedUserNameToCredentialEntryList
+  val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+  var activeEntry: EntryInfo? = null
+  if (sortedUserNameToCredentialEntryList
+      .size == 1 && authenticationEntryList.isEmpty()
+  ) {
+    activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
+  } else if (
+    sortedUserNameToCredentialEntryList
+      .isEmpty() && authenticationEntryList.size == 1
+  ) {
+    activeEntry = authenticationEntryList.first()
+  }
+  return activeEntry
+}
+
 private fun toGetScreenState(
   providerInfoList: List<ProviderInfo>
 ): GetScreenState {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 18d5089..5cb8d3b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -66,7 +66,7 @@
                         it.type,
                         it.credentialRetrievalData,
                         it.candidateQueryData,
-                        it.requireSystemProvider()
+                        it.isSystemProviderRequired()
                     )
                 }
             )
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 21f4be0..1bbdad5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -275,8 +275,11 @@
     }
 
     /**
-     * Returns whether there is only one user on this device, not including
-     * the system-only user.
+     * Returns whether there is only one "full" user on this device.
+     *
+     * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
+     * headless system user mode}, the system user is not "full", so it's not be considered in the
+     * calculation.
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 5c5720a..cc2e3a6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -128,8 +128,7 @@
     }
 
     /**
-     * Returns whether there is only one user on this device, not including
-     * the system-only user.
+     * Returns whether there is only one user on this device.
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
new file mode 100644
index 0000000..9ddd0c6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 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.spa.framework.util
+
+import android.content.res.Resources
+import android.graphics.Typeface
+import android.text.Spanned
+import android.text.style.StyleSpan
+import android.text.style.URLSpan
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Density
+
+const val URLSPAN_TAG = "URLSPAN_TAG"
+
+@Composable
+fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
+    LocalConfiguration.current
+    val resources = LocalContext.current.resources
+    val density = LocalDensity.current
+    return remember(id) {
+        val text = resources.getText(id)
+        spannableStringToAnnotatedString(text, density, urlSpanColor)
+    }
+}
+
+private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
+    return if (text is Spanned) {
+        with(density) {
+            buildAnnotatedString {
+                append((text.toString()))
+                text.getSpans(0, text.length, Any::class.java).forEach {
+                    val start = text.getSpanStart(it)
+                    val end = text.getSpanEnd(it)
+                    when (it) {
+                        is StyleSpan ->
+                            when (it.style) {
+                                Typeface.NORMAL -> addStyle(
+                                        SpanStyle(
+                                                fontWeight = FontWeight.Normal,
+                                                fontStyle = FontStyle.Normal
+                                        ),
+                                        start,
+                                        end
+                                )
+                                Typeface.BOLD -> addStyle(
+                                        SpanStyle(
+                                                fontWeight = FontWeight.Bold,
+                                                fontStyle = FontStyle.Normal
+                                        ),
+                                        start,
+                                        end
+                                )
+                                Typeface.ITALIC -> addStyle(
+                                        SpanStyle(
+                                                fontWeight = FontWeight.Normal,
+                                                fontStyle = FontStyle.Italic
+                                        ),
+                                        start,
+                                        end
+                                )
+                                Typeface.BOLD_ITALIC -> addStyle(
+                                        SpanStyle(
+                                                fontWeight = FontWeight.Bold,
+                                                fontStyle = FontStyle.Italic
+                                        ),
+                                        start,
+                                        end
+                                )
+                            }
+                        is URLSpan -> {
+                            addStyle(
+                                    SpanStyle(
+                                            color = urlSpanColor,
+                                    ),
+                                    start,
+                                    end
+                            )
+                            if (!it.url.isNullOrEmpty()) {
+                                addStringAnnotation(
+                                        URLSPAN_TAG,
+                                        it.url,
+                                        start,
+                                        end
+                                )
+                            }
+                        }
+                        else -> addStyle(SpanStyle(), start, end)
+                    }
+                }
+            }
+        }
+    } else {
+        AnnotatedString(text.toString())
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
index 296cf3b..d9d3b37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -34,15 +34,22 @@
 @Composable
 fun Footer(footerText: String) {
     if (footerText.isEmpty()) return
+    Footer {
+        SettingsBody(footerText)
+    }
+}
+
+@Composable
+fun Footer(content: @Composable () -> Unit) {
     Column(Modifier.padding(SettingsDimension.itemPadding)) {
         Icon(
-            imageVector = Icons.Outlined.Info,
-            contentDescription = null,
-            modifier = Modifier.size(SettingsDimension.itemIconSize),
-            tint = MaterialTheme.colorScheme.onSurfaceVariant,
+                imageVector = Icons.Outlined.Info,
+                contentDescription = null,
+                modifier = Modifier.size(SettingsDimension.itemIconSize),
+                tint = MaterialTheme.colorScheme.onSurfaceVariant,
         )
         Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
-        SettingsBody(footerText)
+        content()
     }
 }
 
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index 1ca425c..cbfea06 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -25,4 +25,6 @@
         =1    {There is one song found in {place}.}
         other {There are # songs found in {place}.}
     }</string>
+
+    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
new file mode 100644
index 0000000..b65be42
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.spa.framework.util
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.URLSPAN_TAG
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnnotatedStringResourceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun testAnnotatedStringResource() {
+        composeTestRule.setContent {
+            val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)
+
+            val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
+            assertThat(annotations).hasSize(1)
+            assertThat(annotations[0].start).isEqualTo(31)
+            assertThat(annotations[0].end).isEqualTo(35)
+            assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
+            assertThat(annotations[0].item).isEqualTo("https://www.google.com/")
+
+            assertThat(annotatedString.spanStyles).hasSize(2)
+            assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
+            assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
+            assertThat(annotatedString.spanStyles[0].item).isEqualTo(
+                    SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))
+
+            assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
+            assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
+            assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 0b45da6..21c9e34 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -29,6 +29,7 @@
     packageName: String,
     userId: Int,
     footerText: String,
+    footerContent: (@Composable () -> Unit)?,
     packageManagers: IPackageManagers,
     content: @Composable PackageInfo.() -> Unit,
 ) {
@@ -40,6 +41,10 @@
 
         packageInfo.content()
 
-        Footer(footerText)
+        if (footerContent != null) {
+            Footer(footerContent)
+        } else {
+            Footer(footerText)
+        }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index e9fcbd2..7c689c6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -141,6 +141,7 @@
         packageName = packageName,
         userId = userId,
         footerText = stringResource(footerResId),
+        footerContent = footerContent(),
         packageManagers = packageManagers,
     ) {
         val model = createSwitchModel(applicationInfo)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 1ab6230..f4b3204 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.ui.text.AnnotatedString
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.rememberContext
@@ -36,7 +37,10 @@
     val footerResId: Int
     val switchRestrictionKeys: List<String>
         get() = emptyList()
-
+    @Composable
+    fun footerContent(): (@Composable () -> Unit)? {
+        return null
+    }
     /**
      * Loads the extra info for the App List, and generates the [AppRecord] List.
      *
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index ae06193..c0818a8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -39,7 +39,7 @@
      */
     public static final String[] SETTINGS_TO_BACKUP = {
         Settings.Global.APPLY_RAMPING_RINGER,
-        Settings.Global.BUGREPORT_IN_POWER_MENU,
+        Settings.Global.BUGREPORT_IN_POWER_MENU,                        // moved to secure
         Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
         Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
         Settings.Global.AUTO_TIME,
@@ -70,8 +70,8 @@
         Settings.Global.ZEN_DURATION,
         Settings.Global.CHARGING_VIBRATION_ENABLED,
         Settings.Global.AWARE_ALLOWED,
-        Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
-        Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,
+        Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,                   // moved to secure
+        Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,                  // moved to secure
         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
         Settings.Global.USER_DISABLED_HDR_FORMATS,
         Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 06c3476..c133097 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -29,7 +29,7 @@
      */
     @UnsupportedAppUsage
     public static final String[] SETTINGS_TO_BACKUP = {
-        Settings.Secure.BUGREPORT_IN_POWER_MENU,                            // moved to global
+        Settings.Secure.BUGREPORT_IN_POWER_MENU,
         Settings.Secure.ALLOW_MOCK_LOCATION,
         Settings.Secure.USB_MASS_STORAGE_ENABLED,                           // moved to global
         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
@@ -225,6 +225,8 @@
         Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
         Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
-        Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME
+        Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+        Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
+        Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index a1a9e8c..7b8ca4d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -306,6 +306,13 @@
         VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
+                new DiscreteValueValidator(
+                        new String[] {
+                                String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_FAILURE),
+                                String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_SUCCESS),
+                        }));
         VALIDATORS.put(Global.Wearable.MASTER_GESTURES_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.UNGAZE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d72d4d5..03921e18 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -357,5 +357,7 @@
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 8d4a35d..ede69be 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -363,9 +363,6 @@
                 Settings.Global.BOOT_COUNT,
                 GlobalSettingsProto.BOOT_COUNT);
         dumpSetting(s, p,
-                Settings.Global.BUGREPORT_IN_POWER_MENU,
-                GlobalSettingsProto.BUGREPORT_IN_POWER_MENU);
-        dumpSetting(s, p,
                 Settings.Global.CACHED_APPS_FREEZER_ENABLED,
                 GlobalSettingsProto.CACHED_APPS_FREEZER_ENABLED);
         dumpSetting(s, p,
@@ -1629,6 +1626,7 @@
 
         // Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated.
         // Settings.Global.APPLY_RAMPING_RINGER intentionally excluded since it's deprecated.
+        // Settings.Global.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
     }
 
     private static void dumpProtoConfigSettingsLocked(
@@ -1840,6 +1838,10 @@
                 Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                 SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
 
+        dumpSetting(s, p,
+                Settings.Secure.BUGREPORT_IN_POWER_MENU,
+                SecureSettingsProto.BUGREPORT_IN_POWER_MENU);
+
         final long aovToken = p.start(SecureSettingsProto.ALWAYS_ON_VPN);
         dumpSetting(s, p,
                 Settings.Secure.ALWAYS_ON_VPN_APP,
@@ -2662,7 +2664,6 @@
         p.end(token);
 
         // Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED intentionally excluded since it's deprecated.
-        // Settings.Secure.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
         // Settings.Secure.ADB_ENABLED intentionally excluded since it's deprecated.
         // Settings.Secure.ALLOW_MOCK_LOCATION intentionally excluded since it's deprecated.
         // Settings.Secure.DATA_ROAMING intentionally excluded since it's deprecated.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1356e1d..f740326 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3681,7 +3681,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 212;
+            private static final int SETTINGS_VERSION = 213;
 
             private final int mUserId;
 
@@ -3791,6 +3791,7 @@
              *     currentVersion = 119;
              * }
              */
+            @GuardedBy("mLock")
             private int onUpgradeLocked(int userId, int oldVersion, int newVersion) {
                 if (DEBUG) {
                     Slog.w(LOG_TAG, "Upgrading settings for user: " + userId + " from version: "
@@ -5588,6 +5589,32 @@
                     currentVersion = 212;
                 }
 
+                if (currentVersion == 212) {
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+                    final Setting bugReportInPowerMenu = globalSettings.getSettingLocked(
+                            Global.BUGREPORT_IN_POWER_MENU);
+
+                    if (!bugReportInPowerMenu.isNull()) {
+                        Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to "
+                                + bugReportInPowerMenu.getValue() + " in Secure settings.");
+                        secureSettings.insertSettingLocked(
+                                Secure.BUGREPORT_IN_POWER_MENU,
+                                bugReportInPowerMenu.getValue(), null /* tag */,
+                                false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                        // set global bug_report_in_power_menu setting to null since it's deprecated
+                        Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null"
+                                + " in Global settings since it's deprecated.");
+                        globalSettings.insertSettingLocked(
+                                Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */,
+                                true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 213;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ee36f3..db7032e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -645,6 +645,7 @@
                     Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
                     Settings.Global.Wearable.SETUP_LOCALE,
                     Settings.Global.Wearable.OEM_SETUP_VERSION,
+                    Settings.Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
                     Settings.Global.Wearable.MASTER_GESTURES_ENABLED,
                     Settings.Global.Wearable.UNGAZE_ENABLED,
                     Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 115cf792..26a68bc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -679,6 +679,20 @@
             android:excludeFromRecents="true"
             android:exported="true" />
 
+        <!-- started from Telecomm(CallsManager) -->
+        <activity
+            android:name=".telephony.ui.activity.SwitchToManagedProfileForCallActivity"
+            android:excludeFromRecents="true"
+            android:exported="true"
+            android:finishOnCloseSystemDialogs="true"
+            android:permission="android.permission.MODIFY_PHONE_STATE"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert">
+            <intent-filter>
+                <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d023..2910bba 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -5,46 +5,72 @@
 SystemUI is a persistent process that provides UI for the system but outside
 of the system_server process.
 
-The starting point for most of sysui code is a list of services that extend
-SystemUI that are started up by SystemUIApplication. These services then depend
-on some custom dependency injection provided by Dependency.
-
 Inputs directed at sysui (as opposed to general listeners) generally come in
 through IStatusBar. Outputs from sysui are through a variety of private APIs to
 the android platform all over.
 
 ## SystemUIApplication
 
-When SystemUIApplication starts up, it will start up the services listed in
-config_systemUIServiceComponents or config_systemUIServiceComponentsPerUser.
+When SystemUIApplication starts up, it instantiates a Dagger graph from which
+various pieces of the application are built.
 
-Each of these services extend SystemUI. SystemUI provides them with a Context
-and gives them callbacks for onConfigurationChanged (this historically was
-the main path for onConfigurationChanged, now also happens through
-ConfigurationController). They also receive a callback for onBootCompleted
+To support customization, SystemUIApplication relies on the AndroidManifest.xml
+having an `android.app.AppComponentFactory` specified. Specifically, it relies
+on an `AppComponentFactory` that subclases `SystemUIAppComponentFactoryBase`.
+Implementations of this abstract base class must override
+`#createSystemUIInitializer(Context)` which returns a `SystemUIInitializer`.
+`SystemUIInitializer` primary job in turn is to intialize and return the Dagger
+root component back to the `SystemUIApplication`.
+
+Writing a custom `SystemUIAppComponentFactoryBase` and `SystemUIInitializer`,
+should be enough for most implementations to stand up a customized Dagger
+graph, and launch a custom version of SystemUI.
+
+## Dagger / Dependency Injection
+
+See [dagger.md](docs/dagger.md) and https://dagger.dev/.
+
+## CoreStartable
+
+The starting point for most of SystemUI code is a list of classes that
+implement `CoreStartable` that are started up by SystemUIApplication.
+CoreStartables are like miniature services. They have their `#start` method
+called after being instantiated, and a reference to them is stored inside
+SystemUIApplication. They are in charge of their own behavior beyond this,
+registering and unregistering with the rest of the system as needed.
+
+`CoreStartable` also receives a callback for `#onBootCompleted`
 since these objects may be started before the device has finished booting.
 
-Each SystemUI service is expected to be a major part of system ui and the
-goal is to minimize communication between them. So in general they should be
-relatively silo'd.
+`CoreStartable` is an ideal place to add new features and functionality
+that does not belong directly under the umbrella of an existing feature.
+It is better to define a new `CoreStartable` than to stick unrelated
+initialization code together in catch-all methods.
 
-## Dependencies
+CoreStartables are tied to application startup via Dagger:
 
-The first SystemUI service that is started should always be Dependency.
-Dependency provides a static method for getting a hold of dependencies that
-have a lifecycle that spans sysui. Dependency has code for how to create all
-dependencies manually added. SystemUIFactory is also capable of
-adding/replacing these dependencies.
+```kotlin
+class FeatureStartable
+@Inject
+constructor(
+    /* ... */
+) : CoreStartable {
+    override fun start() {
+        // ...
+    }
+}
 
-Dependencies are lazily initialized, so if a Dependency is never referenced at
-runtime, it will never be created.
+@Module
+abstract class FeatureModule {
+    @Binds
+    @IntoMap
+    @ClassKey(FeatureStartable::class)
+    abstract fun bind(impl: FeatureStartable): CoreStartable
+}
+```
 
-If an instantiated dependency implements Dumpable it will be included in dumps
-of sysui (and bug reports), allowing it to include current state information.
-This is how \*Controllers dump state to bug reports.
-
-If an instantiated dependency implements ConfigurationChangeReceiver it will
-receive onConfigurationChange callbacks when the configuration changes.
+Including `FeatureModule` in the Dagger graph such as this will ensure that
+`FeatureStartable` gets constructed and that its `#start` method is called.
 
 ## IStatusBar
 
@@ -64,12 +90,6 @@
 This is generally used a shortcut to directly trigger CommandQueue rather than
 calling StatusManager and waiting for the call to come back to IStatusBar.
 
-## Default SystemUI services list
-
-### [com.android.systemui.Dependency](/packages/SystemUI/src/com/android/systemui/Dependency.java)
-
-Provides custom dependency injection.
-
 ### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
 
 Creates/initializes the channels sysui uses when posting notifications.
@@ -88,11 +108,11 @@
 Registers all the callbacks/listeners required to show the Volume dialog when
 it should be shown.
 
-### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
+### [com.android.systemui.status.phone.CentralSurfaces](/packages/SystemUI/src/com/android/systemui/status/phone/CentralSurfaces.java)
 
 This shows the UI for the status bar and the notification shade it contains.
 It also contains a significant amount of other UI that interacts with these
-surfaces (keyguard, AOD, etc.). StatusBar also contains a notification listener
+surfaces (keyguard, AOD, etc.). CentralSurfaces also contains a notification listener
 to receive notification callbacks.
 
 ### [com.android.systemui.usb.StorageNotification](/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java)
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index ed54f08..76139c6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -16,11 +16,19 @@
 
 package com.android.systemui.accessibility.accessibilitymenu;
 
+import android.accessibilityservice.AccessibilityButtonController;
 import android.accessibilityservice.AccessibilityService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
 import android.view.Display;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -28,11 +36,17 @@
 import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
 
 /** @hide */
-public class AccessibilityMenuService extends AccessibilityService implements View.OnTouchListener {
+public class AccessibilityMenuService extends AccessibilityService
+        implements View.OnTouchListener {
     private static final String TAG = "A11yMenuService";
 
     private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
 
+    private long mLastTimeTouchedOutside = 0L;
+    // Timeout used to ignore the A11y button onClick() when ACTION_OUTSIDE is also received on
+    // clicking on the A11y button.
+    public static final long BUTTON_CLICK_TIMEOUT = 200;
+
     private A11yMenuOverlayLayout mA11yMenuLayout;
 
     private static boolean sInitialized = false;
@@ -61,7 +75,7 @@
     };
 
     // Update layout.
-    private final Handler mHandler = new Handler(getMainLooper());
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Runnable mOnConfigChangedRunnable = new Runnable() {
         @Override
         public void run() {
@@ -80,6 +94,28 @@
     @Override
     public void onCreate() {
         super.onCreate();
+
+        getAccessibilityButtonController().registerAccessibilityButtonCallback(
+                new AccessibilityButtonController.AccessibilityButtonCallback() {
+                    /**
+                     * {@inheritDoc}
+                     */
+                    @Override
+                    public void onClicked(AccessibilityButtonController controller) {
+                        if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+                                > BUTTON_CLICK_TIMEOUT) {
+                            mA11yMenuLayout.toggleVisibility();
+                        }
+                    }
+
+                    /**
+                     * {@inheritDoc}
+                     */
+                    @Override
+                    public void onAvailabilityChanged(AccessibilityButtonController controller,
+                            boolean available) {}
+                }
+        );
     }
 
     @Override
@@ -95,8 +131,12 @@
     protected void onServiceConnected() {
         mA11yMenuLayout = new A11yMenuOverlayLayout(this);
 
-        // Temporary measure to force visibility
-        mA11yMenuLayout.toggleVisibility();
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mA11yMenuLayout.hideMenu();
+            }}, new IntentFilter(Intent.ACTION_SCREEN_OFF)
+        );
 
         mDisplayManager = getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener, null);
@@ -124,12 +164,34 @@
                 mOnConfigChangedRunnable, BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE);
     }
 
+    /**
+     * Handles click events of shortcuts.
+     *
+     * @param view the shortcut button being clicked.
+     */
+    public void handleClick(View view) {
+        mA11yMenuLayout.hideMenu();
+    }
+
     @Override
     public void onInterrupt() {
     }
 
     @Override
+    protected boolean onKeyEvent(KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            mA11yMenuLayout.hideMenu();
+        }
+        return false;
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+            if (mA11yMenuLayout.hideMenu()) {
+                mLastTimeTouchedOutside = SystemClock.uptimeMillis();
+            }
+        }
         return false;
     }
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index e3401a9..337814e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -116,7 +116,7 @@
         shortcutIconButton.setOnClickListener(
                 (View v) -> {
                     // Handles shortcut click event by AccessibilityMenuService.
-                    // service.handleClick(v);
+                    mService.handleClick(v);
                 });
     }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index c510b87..cec503c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -66,7 +66,7 @@
         public static final int LARGE_GRID_COLUMN_COUNT = 2;
 
         /** Temporary measure to test both item types. */
-        private static final boolean USE_LARGE_ITEMS = true;
+        private static final boolean USE_LARGE_ITEMS = false;
 
         /**
          * Returns the number of items in the grid view.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9a9236b..a3ed085 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -237,13 +237,17 @@
             openedDialogs.firstOrNull {
                 it.dialog.window.decorView.viewRootImpl == controller.viewRoot
             }
-        val animateFrom =
+        val controller =
             animatedParent?.dialogContentWithBackground?.let {
                 Controller.fromView(it, controller.cuj)
             }
                 ?: controller
 
-        if (animatedParent == null && animateFrom !is LaunchableView) {
+        if (
+            animatedParent == null &&
+                controller is ViewDialogLaunchAnimatorController &&
+                controller.source !is LaunchableView
+        ) {
             // Make sure the View we launch from implements LaunchableView to avoid visibility
             // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
             // from a dialog.
@@ -272,7 +276,7 @@
                 launchAnimator,
                 callback,
                 interactionJankMonitor,
-                animateFrom,
+                controller,
                 onDialogDismissed = { openedDialogs.remove(it) },
                 dialog = dialog,
                 animateBackgroundBoundsChange,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 46d5a5c..9257f99 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@
 /** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
 class ViewDialogLaunchAnimatorController
 internal constructor(
-    private val source: View,
+    internal val source: View,
     override val cuj: DialogCuj?,
 ) : DialogLaunchAnimator.Controller {
     override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
new file mode 100644
index 0000000..1f6e603
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 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.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+/**
+ * Detects violations of the Dependency Rule of Clean Architecture.
+ *
+ * The rule states that code in each layer may only depend on code in the same layer or the layer
+ * directly "beneath" that layer in the layer diagram.
+ *
+ * In System UI, we have three layers; from top to bottom, they are: ui, domain, and data. As a
+ * convention, was used packages with those names to place code in the appropriate layer. We also
+ * make an exception and allow for shared models to live under a separate package named "shared" to
+ * avoid code duplication.
+ *
+ * For more information, please see go/sysui-arch.
+ */
+@Suppress("UnstableApiUsage")
+class CleanArchitectureDependencyViolationDetector : Detector(), Detector.UastScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement>> {
+        return listOf(UFile::class.java)
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return object : UElementHandler() {
+            override fun visitFile(node: UFile) {
+                // Check which Clean Architecture layer this file belongs to:
+                matchingLayer(node.packageName)?.let { layer ->
+                    // The file matches with a Clean Architecture layer. Let's check all of its
+                    // imports.
+                    node.imports.forEach { importStatement ->
+                        visitImportStatement(context, layer, importStatement)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun visitImportStatement(
+        context: JavaContext,
+        layer: Layer,
+        importStatement: UImportStatement,
+    ) {
+        val importText = importStatement.importReference?.asSourceString() ?: return
+        val importedLayer = matchingLayer(importText) ?: return
+
+        // Now check whether the layer of the file may depend on the layer of the import.
+        if (!layer.mayDependOn(importedLayer)) {
+            context.report(
+                issue = ISSUE,
+                scope = importStatement,
+                location = context.getLocation(importStatement),
+                message =
+                    "The ${layer.packageNamePart} layer may not depend on" +
+                        " the ${importedLayer.packageNamePart} layer.",
+            )
+        }
+    }
+
+    private fun matchingLayer(packageName: String): Layer? {
+        val packageNameParts = packageName.split(".").toSet()
+        return Layer.values()
+            .filter { layer -> packageNameParts.contains(layer.packageNamePart) }
+            .takeIf { it.size == 1 }
+            ?.first()
+    }
+
+    private enum class Layer(
+        val packageNamePart: String,
+        val canDependOn: Set<Layer>,
+    ) {
+        SHARED(
+            packageNamePart = "shared",
+            canDependOn = emptySet(), // The shared layer may not depend on any other layer.
+        ),
+        DATA(
+            packageNamePart = "data",
+            canDependOn = setOf(SHARED),
+        ),
+        DOMAIN(
+            packageNamePart = "domain",
+            canDependOn = setOf(SHARED, DATA),
+        ),
+        UI(
+            packageNamePart = "ui",
+            canDependOn = setOf(DOMAIN, SHARED),
+        ),
+        ;
+
+        fun mayDependOn(otherLayer: Layer): Boolean {
+            return this == otherLayer || canDependOn.contains(otherLayer)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        val ISSUE =
+            Issue.create(
+                id = "CleanArchitectureDependencyViolation",
+                briefDescription = "Violation of the Clean Architecture Dependency Rule.",
+                explanation =
+                    """
+                    Following the \"Dependency Rule\" from Clean Architecture, every layer of code \
+                    can only depend code in its own layer or code in the layer directly \
+                    \"beneath\" it. Therefore, the UI layer can only depend on the" Domain layer \
+                    and the Domain layer can only depend on the Data layer. We" do make an \
+                    exception to allow shared models to exist and be shared across layers by \
+                    placing them under shared/model, which should be done with care. For more \
+                    information about Clean Architecture in System UI, please see go/sysui-arch. \
+                    NOTE: if your code is not using Clean Architecture, please feel free to ignore \
+                    this warning.
+                """,
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(
+                        CleanArchitectureDependencyViolationDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE,
+                    ),
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 3f334c1c..254a6fb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,9 +27,11 @@
 class SystemUIIssueRegistry : IssueRegistry() {
 
     override val issues: List<Issue>
-        get() = listOf(
+        get() =
+            listOf(
                 BindServiceOnMainThreadDetector.ISSUE,
                 BroadcastSentViaContextDetector.ISSUE,
+                CleanArchitectureDependencyViolationDetector.ISSUE,
                 SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                 SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
                 NonInjectedMainThreadDetector.ISSUE,
@@ -37,7 +39,7 @@
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
                 StaticSettingsProviderDetector.ISSUE
-        )
+            )
 
     override val api: Int
         get() = CURRENT_API
@@ -45,9 +47,9 @@
         get() = 8
 
     override val vendor: Vendor =
-            Vendor(
-                    vendorName = "Android",
-                    feedbackUrl = "http://b/issues/new?component=78010",
-                    contact = "jernej@google.com"
-            )
+        Vendor(
+            vendorName = "Android",
+            feedbackUrl = "http://b/issues/new?component=78010",
+            contact = "jernej@google.com"
+        )
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
new file mode 100644
index 0000000..a4b59fd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2023 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Ignore
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+@Ignore("b/254533331")
+class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector {
+        return CleanArchitectureDependencyViolationDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            CleanArchitectureDependencyViolationDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun `No violations`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .run()
+            .expectWarningCount(0)
+    }
+
+    @Test
+    fun `Violation - domain depends on ui`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.domain.interactor
+
+                        import test.ui.viewmodel.ViewModel
+
+                        class BadClass(
+                            private val viewModel: ViewModel,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+                    import test.ui.viewmodel.ViewModel
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - ui depends on data`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.ui.viewmodel
+
+                        import test.data.repository.Repository
+
+                        class BadClass(
+                            private val repository: Repository,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+                    import test.data.repository.Repository
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - shared depends on all other layers`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.shared.model
+
+                        import test.data.repository.Repository
+                        import test.domain.interactor.Interactor
+                        import test.ui.viewmodel.ViewModel
+
+                        class BadClass(
+                            private val repository: Repository,
+                            private val interactor: Interactor,
+                            private val viewmodel: ViewModel,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(3)
+            .expect(
+                expectedText =
+                    """
+                    src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+                    import test.data.repository.Repository
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+                    import test.domain.interactor.Interactor
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+                    import test.ui.viewmodel.ViewModel
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 3 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - data depends on domain`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.data.repository
+
+                        import test.domain.interactor.Interactor
+
+                        class BadClass(
+                            private val interactor: Interactor,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+                    import test.domain.interactor.Interactor
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    companion object {
+        private val MODEL_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.shared.model
+
+                    import test.some.other.thing.SomeOtherThing
+
+                    data class Model(
+                        private val name: String,
+                    )
+                """.trimIndent()
+            )
+        private val REPOSITORY_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.data.repository
+
+                    import test.shared.model.Model
+                    import test.some.other.thing.SomeOtherThing
+
+                    class Repository {
+                        private val models = listOf(
+                            Model("one"),
+                            Model("two"),
+                            Model("three"),
+                        )
+
+                        fun getModels(): List<Model> {
+                            return models
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val INTERACTOR_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.domain.interactor
+
+                    import test.data.repository.Repository
+                    import test.shared.model.Model
+
+                    class Interactor(
+                        private val repository: Repository,
+                    ) {
+                        fun getModels(): List<Model> {
+                            return repository.getModels()
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val VIEW_MODEL_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.ui.viewmodel
+
+                    import test.domain.interactor.Interactor
+                    import test.some.other.thing.SomeOtherThing
+
+                    class ViewModel(
+                        private val interactor: Interactor,
+                    ) {
+                        fun getNames(): List<String> {
+                            return interactor.getModels().map { model -> model.name }
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val NON_CLEAN_ARCHITECTURE_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.some.other.thing
+
+                    import test.data.repository.Repository
+                    import test.domain.interactor.Interactor
+                    import test.ui.viewmodel.ViewModel
+
+                    class SomeOtherThing {
+                        init {
+                            val viewModel = ViewModel(
+                                interactor = Interactor(
+                                    repository = Repository(),
+                                ),
+                            )
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val LEGITIMATE_FILES =
+            arrayOf(
+                MODEL_FILE,
+                REPOSITORY_FILE,
+                INTERACTOR_FILE,
+                VIEW_MODEL_FILE,
+                NON_CLEAN_ARCHITECTURE_FILE,
+            )
+    }
+}
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index 8917013..9b4c21e 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -8,105 +8,110 @@
 
  - [User's guide](https://google.github.io/dagger/users-guide)
 
-TODO: Add some links.
-
 ## State of the world
 
-Dagger 2 has been turned on for SystemUI and a early first pass has been taken
-for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
-to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency 
-to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI 
-through a number of CLs.
+Dagger 2 has been turned on for SystemUI and much of
+[Dependency.java](../src/com/android/systemui/Dependency.java)
+has been converted to use Dagger. Since a lot of SystemUI depends on Dependency,
+stubs have been added to Dependency to proxy any gets through to the instances
+provided by dagger, this will allow migration of SystemUI through a number of CLs.
 
 ### How it works in SystemUI
 
+There are three high level "scopes" of concern in SystemUI. They all represent
+singleton scopes, but serve different purposes.
+
+* `@Singleton` - Instances that are shared everywhere. There isn't a  lot of
+   code in this scope. Things like the main thread, and Android Framework
+   provided instances mostly.
+* `@WMShell` - WindowManager related code in the SystemUI process. We don't
+   want this code relying on the rest of SystemUI, and we don't want the rest
+   of SystemUI peeking into its internals, so it runs in its own Subcomponent.
+* `@SysUISingleton` - Most of what would be considered "SystemUI". Most feature
+   work by SystemUI developers goes into this scope. Useful interfaces from
+   WindowManager are made available inside this Subcomponent.
+
+The root dagger graph is created by an instance of `SystemUIInitializer`.
+See [README.md](../README.md) for more details.
 For the classes that we're using in Dependency and are switching to dagger, the
 equivalent dagger version is using `@Singleton` and therefore only has one instance.
 To have the single instance span all of SystemUI and be easily accessible for
 other components, there is a single root `@Component` that exists that generates
-these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
-and is called `SystemUIRootComponent`.
-
-```java
-
-@Singleton
-@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
-        ContextHolder.class})
-public interface SystemUIRootComponent {
-    @Singleton
-    Dependency.DependencyInjector createDependency();
-}
-```
-
-The root component is composed of root modules, which in turn provide the global singleton 
-dependencies across all of SystemUI.
-
-- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
-variants (like other form factors e.g. Car). 
-
-- `DependencyBinder` creates the mapping from interfaces to implementation classes. 
-
-- `DependencyProvider` provides or binds any remaining depedencies required.
-
-### Adding injection to a new SystemUI object
-
-SystemUI object are made injectable by adding an entry in `SystemUIBinder`. SystemUIApplication uses
-information in that file to locate and construct an instance of the requested SystemUI class.
+these. The component lives in
+[ReferenceGlobalRootComponent.java](../src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java).
 
 ### Adding a new injectable object
 
-First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
-instance should be created.
+First annotate the constructor with `@Inject`. Also annotate it with
+`@SysUISingleton` if only one instance should be created.
 
-```java
-@Singleton
-public class SomethingController {
-  @Inject
-  public SomethingController(Context context,
-    @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
-      // context and mainHandler will be automatically populated.
-  }
+```kotlin
+@SysUISingleton
+class FeatureStartable
+@Inject
+constructor(
+/* ... */
+) {
+    // ...
 }
 ```
 
-If you have an interface class and an implementation class, dagger needs to know
-how to map it. The simplest way to do this is to add an `@Provides` method to
-DependencyProvider. The type of the return value tells dagger which dependency it's providing.
+If you have an interface class and an implementation class, Dagger needs to
+know how to map it. The simplest way to do this is to add an `@Binds` method
+in a module. The type of the return value tells dagger which dependency it's
+providing:
 
-```java
-public class DependencyProvider {
-  //...
-  @Singleton
-  @Provides
-  public SomethingController provideSomethingController(Context context,
-      @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
-    return new SomethingControllerImpl(context, mainHandler);
-  }
+```kotlin
+@Module
+abstract class FeatureModule {
+    @Binds
+    abstract fun bindsFeature(impl: FeatureImpl): Feature
 }
 ```
 
-If you need to access this from Dependency#get, then add an adapter to Dependency
-that maps to the instance provided by Dagger. The changes should be similar
-to the following diff.
+If you have a class that you want to make injectable that has can not
+be easily constructed by Dagger, write a `@Provides` method for it:
 
-```java
-public class Dependency {
-  //...
-  @Inject Lazy<SomethingController> mSomethingController;
-  //...
-  public void start() {
-    //...
-    mProviders.put(SomethingController.class, mSomethingController::get);
-  }
+```kotlin
+@Module
+abstract class FeatureModule {
+    @Module
+    companion object {
+        @Provides
+        fun providesFeature(ctx: Context): Feature {
+            return FeatureImpl.constructFromContext(ctx)
+        }
+    }
 }
 ```
 
+### Module Organization
+
+Please define your modules on _at least_ per-package level. If the scope of a
+package grows to encompass a great number of features, create per-feature
+modules.
+
+**Do not create catch-all modules.** Those quickly grow unwieldy and
+unmaintainable. Any that exist today should be refactored into obsolescence.
+
+You can then include your module in one of three places:
+
+1) Within another module that depends on it. Ideally, this creates a clean
+   dependency graph between features and utilities.
+2) For features that should exist in all versions of SystemUI (AOSP and
+   any variants), include the module in
+   [SystemUIModule.java](../src/com/android/systemui/dagger/SystemUIModule.java).
+3) For features that should exist only in AOSP, include the module in
+   [ReferenceSystemUIModule.java](../src/com/android/systemui/dagger/ReferenceSystemUIModule.java).
+   Similarly, if you are working on a custom version of SystemUI and have code
+   specific to your version, include it in a module specific to your version.
+
 ### Using injection with Fragments
 
 Fragments are created as part of the FragmentManager, so they need to be
 setup so the manager knows how to create them. To do that, add a method
 to com.android.systemui.fragments.FragmentService$FragmentCreator that
-returns your fragment class. Thats all thats required, once the method
+returns your fragment class. That is all that is required, once the method
 exists, FragmentService will automatically pick it up and use injection
 whenever your fragment needs to be created.
 
@@ -123,48 +128,11 @@
 FragmentHostManager.get(view).create(NavigationBarFragment.class);
 ```
 
-### Using injection with Views
-
-DO NOT ADD NEW VIEW INJECTION. VIEW INJECTION IS BEING ACTIVELY DEPRECATED.
-
-Needing to inject objects into your View's constructor generally implies you
-are doing more work in your presentation layer than is advisable.
-Instead, create an injected controller for you view, inject into the
-controller, and then attach the view to the controller after inflation.
-
-View injection generally causes headaches while testing, as inflating a view
-(which may in turn inflate other views) implicitly causes a Dagger graph to 
-be stood up, which may or may not contain the appropriately 
-faked/mocked/stubbed objects. It is a hard to control process.
-
 ## Updating Dagger2
 
 We depend on the Dagger source found in external/dagger2. We should automatically pick up on updates
 when that repository is updated.
-
-*Deprecated:*
-
-Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
-into
-[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
-
-The following commands should work, substituting in the version that you are looking for:
-
-````
-cd prebuilts/tools/common/m2/repository/com/google/dagger/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-spi/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.28.1/
-````
-
-Then update `prebuilts/tools/common/m2/Android.bp` to point at your new jars.
  
 ## TODO List
 
- - Eliminate usages of Dependency#get
- - Add links in above TODO
+ - Eliminate usages of Dependency#get: http://b/hotlists/3940788
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index dee0f5c..314c736 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -80,6 +80,7 @@
 internal class HueVibrantSecondary() : Hue {
     val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12),
             Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -88,6 +89,7 @@
 internal class HueVibrantTertiary() : Hue {
     val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25),
             Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -96,6 +98,7 @@
 internal class HueExpressiveSecondary() : Hue {
     val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
             Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -104,6 +107,7 @@
 internal class HueExpressiveTertiary() : Hue {
     val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
             Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -148,11 +152,11 @@
 }
 
 internal class CoreSpec(
-    val a1: TonalSpec,
-    val a2: TonalSpec,
-    val a3: TonalSpec,
-    val n1: TonalSpec,
-    val n2: TonalSpec
+        val a1: TonalSpec,
+        val a2: TonalSpec,
+        val a3: TonalSpec,
+        val n1: TonalSpec,
+        val n2: TonalSpec
 )
 
 enum class Style(internal val coreSpec: CoreSpec) {
@@ -214,51 +218,86 @@
     )),
 }
 
+class TonalPalette {
+    val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+    val allShades: List<Int>
+    val allShadesMapped: Map<Int, Int>
+    val baseColor: Int
+
+    internal constructor(spec: TonalSpec, seedColor: Int) {
+        val seedCam = Cam.fromInt(seedColor)
+        allShades = spec.shades(seedCam)
+        allShadesMapped = shadeKeys.zip(allShades).toMap()
+
+        val h = spec.hue.get(seedCam).toFloat()
+        val c = spec.chroma.get(seedCam).toFloat()
+        baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+    }
+
+    val s10: Int get() = this.allShades[0]
+    val s50: Int get() = this.allShades[1]
+    val s100: Int get() = this.allShades[2]
+    val s200: Int get() = this.allShades[3]
+    val s300: Int get() = this.allShades[4]
+    val s400: Int get() = this.allShades[5]
+    val s500: Int get() = this.allShades[6]
+    val s600: Int get() = this.allShades[7]
+    val s700: Int get() = this.allShades[8]
+    val s800: Int get() = this.allShades[9]
+    val s900: Int get() = this.allShades[10]
+    val s1000: Int get() = this.allShades[11]
+}
+
 class ColorScheme(
-    @ColorInt val seed: Int,
-    val darkTheme: Boolean,
-    val style: Style = Style.TONAL_SPOT
+        @ColorInt val seed: Int,
+        val darkTheme: Boolean,
+        val style: Style = Style.TONAL_SPOT
 ) {
 
-    val accent1: List<Int>
-    val accent2: List<Int>
-    val accent3: List<Int>
-    val neutral1: List<Int>
-    val neutral2: List<Int>
+    val accent1: TonalPalette
+    val accent2: TonalPalette
+    val accent3: TonalPalette
+    val neutral1: TonalPalette
+    val neutral2: TonalPalette
 
     constructor(@ColorInt seed: Int, darkTheme: Boolean) :
             this(seed, darkTheme, Style.TONAL_SPOT)
 
     @JvmOverloads
     constructor(
-        wallpaperColors: WallpaperColors,
-        darkTheme: Boolean,
-        style: Style = Style.TONAL_SPOT
+            wallpaperColors: WallpaperColors,
+            darkTheme: Boolean,
+            style: Style = Style.TONAL_SPOT
     ) :
             this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
 
+    val allHues: List<TonalPalette>
+        get() {
+            return listOf(accent1, accent2, accent3, neutral1, neutral2)
+        }
+
     val allAccentColors: List<Int>
         get() {
             val allColors = mutableListOf<Int>()
-            allColors.addAll(accent1)
-            allColors.addAll(accent2)
-            allColors.addAll(accent3)
+            allColors.addAll(accent1.allShades)
+            allColors.addAll(accent2.allShades)
+            allColors.addAll(accent3.allShades)
             return allColors
         }
 
     val allNeutralColors: List<Int>
         get() {
             val allColors = mutableListOf<Int>()
-            allColors.addAll(neutral1)
-            allColors.addAll(neutral2)
+            allColors.addAll(neutral1.allShades)
+            allColors.addAll(neutral2.allShades)
             return allColors
         }
 
     val backgroundColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
 
     val accentColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
 
     init {
         val proposedSeedCam = Cam.fromInt(seed)
@@ -269,24 +308,26 @@
         } else {
             seed
         }
-        val camSeed = Cam.fromInt(seedArgb)
-        accent1 = style.coreSpec.a1.shades(camSeed)
-        accent2 = style.coreSpec.a2.shades(camSeed)
-        accent3 = style.coreSpec.a3.shades(camSeed)
-        neutral1 = style.coreSpec.n1.shades(camSeed)
-        neutral2 = style.coreSpec.n2.shades(camSeed)
+
+        accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
+        accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
+        accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
+        neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
+        neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
     }
 
+    val shadeCount get() = this.accent1.allShades.size
+
     override fun toString(): String {
         return "ColorScheme {\n" +
                 "  seed color: ${stringForColor(seed)}\n" +
                 "  style: $style\n" +
                 "  palettes: \n" +
-                "  ${humanReadable("PRIMARY", accent1)}\n" +
-                "  ${humanReadable("SECONDARY", accent2)}\n" +
-                "  ${humanReadable("TERTIARY", accent3)}\n" +
-                "  ${humanReadable("NEUTRAL", neutral1)}\n" +
-                "  ${humanReadable("NEUTRAL VARIANT", neutral2)}\n" +
+                "  ${humanReadable("PRIMARY", accent1.allShades)}\n" +
+                "  ${humanReadable("SECONDARY", accent2.allShades)}\n" +
+                "  ${humanReadable("TERTIARY", accent3.allShades)}\n" +
+                "  ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
+                "  ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
                 "}"
     }
 
@@ -385,7 +426,8 @@
                     val existingSeedNearby = seeds.find {
                         val hueA = intToCam[int]!!.hue
                         val hueB = intToCam[it]!!.hue
-                        hueDiff(hueA, hueB) < i } != null
+                        hueDiff(hueA, hueB) < i
+                    } != null
                     if (existingSeedNearby) {
                         continue
                     }
@@ -460,9 +502,9 @@
         }
 
         private fun huePopulations(
-            camByColor: Map<Int, Cam>,
-            populationByColor: Map<Int, Double>,
-            filter: Boolean = true
+                camByColor: Map<Int, Cam>,
+                populationByColor: Map<Int, Double>,
+                filter: Boolean = true
         ): List<Double> {
             val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
 
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..ef1406c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,42Q5.8,42 4.9,41.1Q4,40.2 4,39V13.35Q4,12.15 4.9,11.25Q5.8,10.35 7,10.35H14.35L18,6H30L33.65,10.35H41Q42.2,10.35 43.1,11.25Q44,12.15 44,13.35V39Q44,40.2 43.1,41.1Q42.2,42 41,42ZM7,39H41Q41,39 41,39Q41,39 41,39V13.35Q41,13.35 41,13.35Q41,13.35 41,13.35H7Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM7,39Q7,39 7,39Q7,39 7,39V13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM24,34.7Q27.5,34.7 30,32.225Q32.5,29.75 32.5,26.2Q32.5,22.7 30,20.2Q27.5,17.7 24,17.7Q20.45,17.7 17.975,20.2Q15.5,22.7 15.5,26.2Q15.5,29.75 17.975,32.225Q20.45,34.7 24,34.7ZM24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_videocam.xml b/packages/SystemUI/res/drawable/ic_videocam.xml
new file mode 100644
index 0000000..de2bc7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H33Q34.2,8 35.1,8.9Q36,9.8 36,11V21.75L44,13.75V34.25L36,26.25V37Q36,38.2 35.1,39.1Q34.2,40 33,40ZM7,37H33Q33,37 33,37Q33,37 33,37V11Q33,11 33,11Q33,11 33,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 5aa6080..d1a2cf4 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -25,6 +25,7 @@
     android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:paddingStart="8dp"
     >
 
         <LinearLayout
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index bbb8df1c..db94c92 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -26,6 +26,17 @@
         android:id="@+id/content"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/unlock_prompt_footer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center"
+            android:drawablePadding="8dp"
+            android:visibility="gone"
+            android:textAppearance="?android:attr/textAppearanceButton"
+            android:text="@string/unlock_to_see_notif_text"/>
         <com.android.systemui.statusbar.notification.row.FooterViewButton
             style="@style/TextAppearance.NotificationSectionHeaderButton"
             android:id="@+id/manage_text"
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 6c7cab5..5d78e4e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -28,6 +28,7 @@
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
+    <dimen name="qs_panel_padding">24dp</dimen>
     <dimen name="qs_content_horizontal_padding">24dp</dimen>
     <dimen name="qs_horizontal_margin">24dp</dimen>
     <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6043a3c..3d3bb6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1511,10 +1511,10 @@
     <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
-    <string name="notification_channel_summary_default">May ring or vibrate based on phone settings</string>
+    <string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
 
     <!-- [CHAR LIMIT=150] Conversation Notification Importance title: normal conversation level, with bubbling summary -->
-    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on phone settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
+    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on device settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: automatic importance level summary -->
     <string name="notification_channel_summary_automatic">Have the system determine if this notification should make sound or vibration</string>
@@ -2898,4 +2898,25 @@
     <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
     <!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
     <string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
+
+    <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
+    <string name="stylus_battery_low">Stylus battery low</string>
+
+    <!-- Label for a lock screen shortcut to start the camera in video mode. [CHAR_LIMIT=16] -->
+    <string name="video_camera">Video camera</string>
+
+    <!-- Switch to work profile dialer app for placing a call dialog. -->
+    <!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_title">Can\'t call from this profile</string>
+    <!-- Text for switch to work profile for call dialog to guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=NONE]
+    -->
+    <string name="call_from_work_profile_text">Your work policy allows you to make phone calls only from the work profile</string>
+    <!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_action">Switch to work profile</string>
+    <!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_close">Close</string>
 </resources>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 06d425c..bf576dc 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,105 +14,73 @@
   ~ limitations under the License.
   -->
 
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/large_screen_header_constraint">
 
-    <Constraint
-        android:id="@+id/clock">
+    <Constraint android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/date"
-            app:layout_constraintHorizontal_bias="0"
-        />
-        <Transform
-            android:scaleX="1"
-            android:scaleY="1"
-            />
+            app:layout_constraintStart_toEndOf="@id/begin_guide"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/date">
+    <Constraint android:id="@+id/date">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
+            android:layout_marginStart="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="0"
-        />
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/carrier_group">
+    <Constraint android:id="@+id/carrier_group">
         <Layout
-            app:layout_constraintWidth_min="48dp"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            app:layout_constrainedWidth="true"
             android:layout_gravity="end|center_vertical"
-            android:layout_marginStart="8dp"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_default="wrap"
+            app:layout_constraintWidth_min="48dp" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/statusIcons">
+    <Constraint android:id="@+id/statusIcons">
         <Layout
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
+    <Constraint android:id="@+id/batteryRemainingIcon">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
-            app:layout_constraintEnd_toStartOf="@id/privacy_container"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-        />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/privacy_container"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/privacy_container">
+    <Constraint android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-            app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
-            app:layout_constraintHorizontal_bias="1"
-        />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent" />
     </Constraint>
-
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
new file mode 100644
index 0000000..a9a5cf9
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.shared.hardware
+
+import android.view.InputDevice
+
+/**
+ * Returns true if [InputDevice] is electronic components to allow a user to use an active stylus in
+ * the host device or a passive stylus is detected by the host device.
+ */
+val InputDevice.isInternalStylusSource: Boolean
+    get() = isAnyStylusSource && !isExternal
+
+/** Returns true if [InputDevice] is an active stylus. */
+val InputDevice.isExternalStylusSource: Boolean
+    get() = isAnyStylusSource && isExternal
+
+/**
+ * Returns true if [InputDevice] supports any stylus source.
+ *
+ * @see InputDevice.isInternalStylusSource
+ * @see InputDevice.isExternalStylusSource
+ */
+val InputDevice.isAnyStylusSource: Boolean
+    get() = supportsSource(InputDevice.SOURCE_STYLUS)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
new file mode 100644
index 0000000..f020b4e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.shared.hardware
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+
+/**
+ * Gets information about all input devices in the system and returns as a lazy [Sequence].
+ *
+ * For performance reasons, it is preferred to operate atop the returned [Sequence] to ensure each
+ * operation is executed on an element-per-element basis yet customizable.
+ *
+ * For example:
+ * ```kotlin
+ * val stylusDevices = inputManager.getInputDeviceSequence().filter {
+ *   it.supportsSource(InputDevice.SOURCE_STYLUS)
+ * }
+ *
+ * val hasInternalStylus = stylusDevices.any { it.isInternal }
+ * val hasExternalStylus = stylusDevices.any { !it.isInternal }
+ * ```
+ *
+ * @return a [Sequence] of [InputDevice].
+ */
+fun InputManager.getInputDeviceSequence(): Sequence<InputDevice> =
+    inputDeviceIds.asSequence().mapNotNull { getInputDevice(it) }
+
+/**
+ * Returns the first [InputDevice] matching the given predicate, or null if no such [InputDevice]
+ * was found.
+ */
+fun InputManager.findInputDevice(predicate: (InputDevice) -> Boolean): InputDevice? =
+    getInputDeviceSequence().find { predicate(it) }
+
+/**
+ * Returns true if [any] [InputDevice] matches with [predicate].
+ *
+ * For example:
+ * ```kotlin
+ * val hasStylusSupport = inputManager.hasInputDevice { it.isStylusSupport() }
+ * val hasStylusPen = inputManager.hasInputDevice { it.isStylusPen() }
+ * ```
+ */
+fun InputManager.hasInputDevice(predicate: (InputDevice) -> Boolean): Boolean =
+    getInputDeviceSequence().any { predicate(it) }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isInternalStylusSource]. */
+fun InputManager.hasInternalStylusSource(): Boolean = hasInputDevice { it.isInternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isExternalStylusSource]. */
+fun InputManager.hasExternalStylusSource(): Boolean = hasInputDevice { it.isExternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isAnyStylusSource]. */
+fun InputManager.hasAnyStylusSource(): Boolean = hasInputDevice { it.isAnyStylusSource }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 1c532fe..b8bddd1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.view.MotionEvent;
+import com.android.internal.util.ScreenshotRequest;
 
 import com.android.systemui.shared.recents.model.Task;
 
@@ -87,12 +88,6 @@
     void notifyPrioritizedRotation(int rotation) = 25;
 
     /**
-     * Handle the provided image as if it was a screenshot.
-     */
-    void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
-              in Insets visibleInsets, in Task.TaskKey task) = 28;
-
-    /**
      * Notifies to expand notification panel.
      */
     void expandNotificationPanel() = 29;
@@ -125,5 +120,10 @@
      */
     void toggleNotificationPanel() = 50;
 
-    // Next id = 51
+    /**
+     * Handle the screenshot request.
+     */
+    void takeScreenshot(in ScreenshotRequest request) = 51;
+
+    // Next id = 52
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
new file mode 100644
index 0000000..249b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** Logging helper for trust repository. */
+@SysUISingleton
+class TrustRepositoryLogger
+@Inject
+constructor(
+    @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
+) {
+    fun onTrustChanged(
+        enabled: Boolean,
+        newlyUnlocked: Boolean,
+        userId: Int,
+        flags: Int,
+        trustGrantedMessages: List<String>?
+    ) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = enabled
+                bool2 = newlyUnlocked
+                int1 = userId
+                int2 = flags
+                str1 = trustGrantedMessages?.joinToString()
+            },
+            {
+                "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
+                    "userId: $int1, flags: $int2, grantMessages: $str1"
+            }
+        )
+    }
+
+    fun trustListenerRegistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
+    }
+
+    fun trustListenerUnregistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
+    }
+
+    fun trustModelEmitted(value: TrustModel) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = value.userId
+                bool1 = value.isTrusted
+            },
+            { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
+        )
+    }
+
+    fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = isCurrentUserTrusted },
+            { "isCurrentUserTrusted emitted: $bool1" }
+        )
+    }
+
+    companion object {
+        const val TAG = "TrustRepositoryLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 50449b0..f519796 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -42,7 +42,6 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
@@ -343,6 +342,7 @@
 
     /**
      * Register a system action.
+     *
      * @param actionId the action ID to register.
      */
     public void register(int actionId) {
@@ -440,6 +440,7 @@
 
     /**
      * Unregister a system action.
+     *
      * @param actionId the action ID to unregister.
      */
     public void unregister(int actionId) {
@@ -475,7 +476,8 @@
     }
 
     private void handleNotifications() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
+        mCentralSurfacesOptionalLazy.get().ifPresent(
+                CentralSurfaces::animateExpandNotificationsPanel);
     }
 
     private void handleQuickSettings() {
@@ -507,7 +509,7 @@
 
     private void handleTakeScreenshot() {
         ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+        screenshotHelper.takeScreenshot(
                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 867faf9..cc43e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -20,15 +20,13 @@
 import android.content.Intent
 import android.provider.MediaStore
 import android.text.TextUtils
-
 import com.android.systemui.R
 
 class CameraIntents {
     companion object {
-        val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
-                MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
-        val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
-                MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+        val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
         const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
 
         @JvmStatic
@@ -44,18 +42,14 @@
         @JvmStatic
         fun getInsecureCameraIntent(context: Context): Intent {
             val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let {
-                intent.setPackage(it)
-            }
+            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
             return intent
         }
 
         @JvmStatic
         fun getSecureCameraIntent(context: Context): Intent {
             val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let {
-                intent.setPackage(it)
-            }
+            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
             return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
         }
 
@@ -68,5 +62,11 @@
         fun isInsecureCameraIntent(intent: Intent?): Boolean {
             return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
         }
+
+        /** Returns an [Intent] that can be used to start the camera in video mode. */
+        @JvmStatic
+        fun getVideoCameraIntent(): Intent {
+            return Intent(VIDEO_CAMERA_INTENT_ACTION)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index cf02f8f..a434617 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -21,7 +21,9 @@
 import javax.inject.Inject
 
 /** Injectable wrapper around [CameraIntents]. */
-class CameraIntentsWrapper @Inject constructor(
+class CameraIntentsWrapper
+@Inject
+constructor(
     private val context: Context,
 ) {
 
@@ -40,4 +42,9 @@
     fun getInsecureCameraIntent(): Intent {
         return CameraIntents.getInsecureCameraIntent(context)
     }
+
+    /** Returns an [Intent] that can be used to start the camera in video mode. */
+    fun getVideoCameraIntent(): Intent {
+        return CameraIntents.getVideoCameraIntent()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c53b4d1..2539828 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,6 +102,11 @@
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
+    // TODO(b/263414400): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ANIMATE_BIG_PICTURE =
+        unreleasedFlag(120, "notification_animate_big_picture", teamfood = true)
+
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -531,4 +536,9 @@
 
     // TODO(b259590361): Tracking bug
     val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+
+    // 2600 - keyboard shortcut
+    // TODO(b/259352579): Tracking Bug
+    @JvmField
+    val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index c3e163f..718b9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -21,7 +21,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -731,9 +730,10 @@
     }
 
     @VisibleForTesting
-    boolean shouldDisplayBugReport(UserInfo currentUser) {
-        return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
-                && (currentUser == null || currentUser.isAdmin());
+    boolean shouldDisplayBugReport(@Nullable UserInfo user) {
+        return user != null && user.isAdmin()
+                && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
+                user.id) != 0;
     }
 
     @Override
@@ -959,8 +959,7 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
-                            SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
+                    mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                     mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
                 }
@@ -1059,7 +1058,7 @@
         @Override
         public boolean showBeforeProvisioning() {
             return Build.isDebuggable() && mGlobalSettings.getIntForUser(
-                    Settings.Global.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
+                    Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                     && getCurrentUser().isAdmin();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 18854e5..a120e17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -148,12 +148,12 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 
-import dagger.Lazy;
-
 /**
  * Mediates requests related to the keyguard.  This includes queries about the
  * state of the keyguard, power management events that effect whether the keyguard
@@ -589,12 +589,6 @@
         @Override
         public void onDeviceProvisioned() {
             sendUserPresentBroadcast();
-            synchronized (KeyguardViewMediator.this) {
-                // If system user is provisioned, we might want to lock now to avoid showing launcher
-                if (mustNotUnlockCurrentUser()) {
-                    doKeyguardLocked(null);
-                }
-            }
         }
 
         @Override
@@ -1265,11 +1259,6 @@
         mPM.userActivity(SystemClock.uptimeMillis(), false);
     }
 
-    boolean mustNotUnlockCurrentUser() {
-        return UserManager.isSplitSystemUser()
-                && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM;
-    }
-
     private void setupLocked() {
         mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
         mShowKeyguardWakeLock.setReferenceCounted(false);
@@ -1947,31 +1936,28 @@
             }
         }
 
-        // In split system user mode, we never unlock system user.
-        if (!mustNotUnlockCurrentUser()
-                || !mUpdateMonitor.isDeviceProvisioned()) {
+        // if the setup wizard hasn't run yet, don't show
+        final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
+        final boolean absent = SubscriptionManager.isValidSubscriptionId(
+                mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
+        final boolean disabled = SubscriptionManager.isValidSubscriptionId(
+                mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
+        final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
+                || ((absent || disabled) && requireSim);
 
-            // if the setup wizard hasn't run yet, don't show
-            final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
-            final boolean absent = SubscriptionManager.isValidSubscriptionId(
-                    mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
-            final boolean disabled = SubscriptionManager.isValidSubscriptionId(
-                    mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
-            final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
-                    || ((absent || disabled) && requireSim);
-
-            if (!lockedOrMissing && shouldWaitForProvisioning()) {
-                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
-                        + " and the sim is not locked or missing");
-                return;
+        if (!lockedOrMissing && shouldWaitForProvisioning()) {
+            if (DEBUG) {
+                Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+                        + " not locked or missing");
             }
+            return;
+        }
 
-            boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
-            if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
-                    && !lockedOrMissing && !forceShow) {
-                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
-                return;
-            }
+        boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
+        if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+                && !lockedOrMissing && !forceShow) {
+            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+            return;
         }
 
         if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
@@ -2539,15 +2525,6 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
-            if (mustNotUnlockCurrentUser()) {
-                // In split system user mode, we never unlock system user. The end user has to
-                // switch to another user.
-                // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
-                // still completes and makes the screen blank.
-                if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
-                mKeyguardExitAnimationRunner = null;
-                return;
-            }
             mHiding = true;
 
             if (mShowing && !mOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index ea5b4f4..cd4dac0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -30,5 +30,6 @@
     const val HOME_CONTROLS = "home"
     const val QR_CODE_SCANNER = "qr_code_scanner"
     const val QUICK_ACCESS_WALLET = "wallet"
+    const val VIDEO_CAMERA = "video_camera"
     // Please keep alphabetical order of const names to simplify future maintenance.
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index dbc376e..f6e6d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.StatusBarState
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -46,7 +47,7 @@
         get() = context.getString(R.string.accessibility_camera_button)
 
     override val pickerIconResourceId: Int
-        get() = com.android.internal.R.drawable.perm_group_camera
+        get() = R.drawable.ic_camera
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
         get() =
@@ -54,12 +55,20 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon =
                         Icon.Resource(
-                            com.android.internal.R.drawable.perm_group_camera,
+                            R.drawable.ic_camera,
                             ContentDescription.Resource(R.string.accessibility_camera_button)
                         )
                 )
             )
 
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return if (isLaunchable()) {
+            super.getPickerScreenState()
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+    }
+
     override fun onTriggered(
         expandable: Expandable?
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -68,4 +77,8 @@
             .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
+
+    private fun isLaunchable(): Boolean {
+        return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index 71d01eb..a1cce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -39,6 +39,7 @@
             quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
             qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
             camera: CameraQuickAffordanceConfig,
+            videoCamera: VideoCameraQuickAffordanceConfig,
         ): Set<KeyguardQuickAffordanceConfig> {
             return setOf(
                 camera,
@@ -47,6 +48,7 @@
                 home,
                 quickAccessWallet,
                 qrCodeScanner,
+                videoCamera,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 680c06b..4ba2eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -100,9 +100,9 @@
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
         return when {
-            !walletController.isWalletEnabled ->
+            !walletController.walletClient.isWalletServiceAvailable ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-            walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+            !walletController.isWalletEnabled || queryCards().isEmpty() -> {
                 val componentName =
                     walletController.walletClient.createWalletSettingsIntent().toComponentName()
                 val actionText =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d9ec3b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -0,0 +1,105 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class VideoCameraQuickAffordanceConfig
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val cameraIntents: CameraIntentsWrapper,
+    private val activityIntentHelper: ActivityIntentHelper,
+    private val userTracker: UserTracker,
+) : KeyguardQuickAffordanceConfig {
+
+    private val intent: Intent by lazy {
+        cameraIntents.getVideoCameraIntent().apply {
+            putExtra(
+                CameraIntents.EXTRA_LAUNCH_SOURCE,
+                StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
+            )
+        }
+    }
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
+
+    override val pickerName: String
+        get() = context.getString(R.string.video_camera)
+
+    override val pickerIconResourceId: Int
+        get() = R.drawable.ic_videocam
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+        get() =
+            flowOf(
+                if (isLaunchable()) {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        icon =
+                            Icon.Resource(
+                                R.drawable.ic_videocam,
+                                ContentDescription.Resource(R.string.video_camera)
+                            )
+                    )
+                } else {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+                }
+            )
+
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return if (isLaunchable()) {
+            super.getPickerScreenState()
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+    }
+
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+            intent = intent,
+            canShowWhileLocked = false,
+        )
+    }
+
+    private fun isLaunchable(): Boolean {
+        return activityIntentHelper.getTargetActivityInfo(
+            intent,
+            userTracker.userId,
+            true,
+        ) != null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
new file mode 100644
index 0000000..d90f328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+interface TrustRepository {
+    /** Flow representing whether the current user is trusted. */
+    val isCurrentUserTrusted: Flow<Boolean>
+}
+
+@SysUISingleton
+class TrustRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val userRepository: UserRepository,
+    private val trustManager: TrustManager,
+    private val logger: TrustRepositoryLogger,
+) : TrustRepository {
+    private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+
+    private val trust =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TrustManager.TrustListener {
+                        override fun onTrustChanged(
+                            enabled: Boolean,
+                            newlyUnlocked: Boolean,
+                            userId: Int,
+                            flags: Int,
+                            grantMsgs: List<String>?
+                        ) {
+                            logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
+                            trySendWithFailureLogging(
+                                TrustModel(enabled, userId),
+                                TrustRepositoryLogger.TAG,
+                                "onTrustChanged"
+                            )
+                        }
+
+                        override fun onTrustError(message: CharSequence?) = Unit
+
+                        override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
+                    }
+                trustManager.registerTrustListener(callback)
+                logger.trustListenerRegistered()
+                awaitClose {
+                    logger.trustListenerUnregistered()
+                    trustManager.unregisterTrustListener(callback)
+                }
+            }
+            .onEach {
+                latestTrustModelForUser[it.userId] = it
+                logger.trustModelEmitted(it)
+            }
+            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+    override val isCurrentUserTrusted: Flow<Boolean>
+        get() =
+            combine(trust, userRepository.selectedUserInfo, ::Pair)
+                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+                .distinctUntilChanged()
+                .onEach { logger.isCurrentUserTrusted(it) }
+                .onStart { emit(false) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7134ec0..81a5828 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -31,8 +31,10 @@
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -87,6 +89,9 @@
     private fun listenForDreamingToOccluded() {
         scope.launch {
             keyguardInteractor.isDreaming
+                // Add a slight delay, as dreaming and occluded events will arrive with a small gap
+                // in time. This prevents a transition to OCCLUSION happening prematurely.
+                .onEach { delay(50) }
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 9203a9b..14f918d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -50,10 +50,9 @@
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (isAbleToDream, keyguardState) = pair
-                    if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isAbleToDream, lastStartedStep) ->
+                    if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
@@ -72,15 +71,15 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
                     ::toTriple
                 )
-                .collect { (wakefulnessState, keyguardState, isAodAvailable) ->
+                .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
                     if (
-                        keyguardState == KeyguardState.GONE &&
+                        lastStartedStep.to == KeyguardState.GONE &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
                         keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index a92540d..6679b22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -20,8 +20,6 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.os.Handler
 import android.os.Trace
-import android.os.UserHandle
-import android.os.UserManager
 import android.view.View
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -134,12 +132,6 @@
             return
         }
 
-        val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
-        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
-            // In split system user mode, we never unlock system user.
-            return
-        }
-
         Trace.beginSection("KeyguardBouncer#show")
         repository.setPrimaryScrimmed(isScrimmed)
         if (isScrimmed) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
new file mode 100644
index 0000000..4fd14b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Represents the trust state */
+data class TrustModel(
+    /** If true, the system believes the environment to be trusted. */
+    val isTrusted: Boolean,
+    /** The user, for which the trust changed. */
+    val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 93be6a7..5c65c8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -162,8 +162,8 @@
                     context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
                         UI_MODE_NIGHT_YES
                 )
-                    colorScheme.accent1[2]
-                else colorScheme.accent1[3]
+                    colorScheme.accent1.s100
+                else colorScheme.accent1.s200
             },
             { seamlessColor: Int ->
                 val accentColorList = ColorStateList.valueOf(seamlessColor)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 82abf9b..2a8362b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -19,28 +19,28 @@
 import com.android.systemui.monet.ColorScheme
 
 /** Returns the surface color for media controls based on the scheme. */
-internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2[9] // A2-800
+internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2.s800 // A2-800
 
 /** Returns the primary accent color for media controls based on the scheme. */
-internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1[2] // A1-100
+internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1.s100 // A1-100
 
 /** Returns the secondary accent color for media controls based on the scheme. */
-internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1[3] // A1-200
+internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1.s200 // A1-200
 
 /** Returns the primary text color for media controls based on the scheme. */
-internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1[1] // N1-50
+internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1.s50 // N1-50
 
 /** Returns the inverse of the primary text color for media controls based on the scheme. */
-internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1[10] // N1-900
+internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1.s900 // N1-900
 
 /** Returns the secondary text color for media controls based on the scheme. */
-internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] // N2-200
+internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s200 // N2-200
 
 /** Returns the tertiary text color for media controls based on the scheme. */
-internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400
+internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s400 // N2-400
 
 /** Returns the color for the start of the background gradient based on the scheme. */
-internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700
+internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2.s700 // A2-700
 
 /** Returns the color for the end of the background gradient based on the scheme. */
-internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700
+internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1.s700 // A1-700
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 5b137e9..9cf672b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -493,20 +493,20 @@
         ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors,
                 isDarkTheme);
         if (isDarkTheme) {
-            mColorItemContent = mCurrentColorScheme.getAccent1().get(2); // A1-100
-            mColorSeekbarProgress = mCurrentColorScheme.getAccent2().get(7); // A2-600
-            mColorButtonBackground = mCurrentColorScheme.getAccent1().get(4); // A1-300
-            mColorItemBackground = mCurrentColorScheme.getNeutral2().get(9); // N2-800
-            mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().get(9); // A2-800
-            mColorPositiveButtonText = mCurrentColorScheme.getAccent2().get(9); // A2-800
-            mColorDialogBackground = mCurrentColorScheme.getNeutral1().get(10); // N1-900
+            mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+            mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600
+            mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+            mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800
+            mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+            mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+            mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900
         } else {
-            mColorItemContent = mCurrentColorScheme.getAccent1().get(9); // A1-800
-            mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(4); // A1-300
-            mColorButtonBackground = mCurrentColorScheme.getAccent1().get(7); // A1-600
-            mColorItemBackground = mCurrentColorScheme.getAccent2().get(1); // A2-50
-            mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().get(2); // A1-100
-            mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().get(1); // N1-50
+            mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800
+            mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+            mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600
+            mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50
+            mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+            mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50
             mColorDialogBackground = mCurrentColorScheme.getBackgroundColor();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4d005be..543f1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
@@ -44,8 +43,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Binder;
@@ -77,6 +74,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -94,7 +92,6 @@
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -322,18 +319,8 @@
         }
 
         @Override
-        public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
-                Insets visibleInsets, Task.TaskKey task) {
-            mScreenshotHelper.provideScreenshot(
-                    screenImageBundle,
-                    locationInScreen,
-                    visibleInsets,
-                    task.id,
-                    task.userId,
-                    task.sourceComponent,
-                    SCREENSHOT_OVERVIEW,
-                    mHandler,
-                    null);
+        public void takeScreenshot(ScreenshotRequest request) {
+            mScreenshotHelper.takeScreenshot(request, mHandler, null);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 95cc0dc..f011aab 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -19,27 +19,26 @@
 import android.graphics.Insets
 import android.util.Log
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import java.util.function.Consumer
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import java.util.function.Consumer
+import javax.inject.Inject
 
 /**
  * Processes a screenshot request sent from {@link ScreenshotHelper}.
  */
 @SysUISingleton
 class RequestProcessor @Inject constructor(
-    private val capture: ImageCapture,
-    private val policy: ScreenshotPolicy,
-    private val flags: FeatureFlags,
-    /** For the Java Async version, to invoke the callback. */
-    @Application private val mainScope: CoroutineScope
+        private val capture: ImageCapture,
+        private val policy: ScreenshotPolicy,
+        private val flags: FeatureFlags,
+        /** For the Java Async version, to invoke the callback. */
+        @Application private val mainScope: CoroutineScope
 ) {
     /**
      * Inspects the incoming request, returning a potentially modified request depending on policy.
@@ -58,7 +57,7 @@
         // regardless of the managed profile status.
 
         if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
-            flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
         ) {
 
             val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
@@ -66,17 +65,21 @@
 
             result = if (policy.isManagedProfile(info.user.identifier)) {
                 val image = capture.captureTask(info.taskId)
-                    ?: error("Task snapshot returned a null Bitmap!")
+                        ?: error("Task snapshot returned a null Bitmap!")
 
                 // Provide the task snapshot as the screenshot
-                ScreenshotRequest(
-                    TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
-                    HardwareBitmapBundler.hardwareBitmapToBundle(image),
-                    info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
-                )
+                ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
+                        .setTopComponent(info.component)
+                        .setTaskId(info.taskId)
+                        .setUserId(info.user.identifier)
+                        .setBitmap(image)
+                        .setBoundsOnScreen(info.bounds)
+                        .setInsets(Insets.NONE)
+                        .build()
             } else {
                 // Create a new request of the same type which includes the top component
-                ScreenshotRequest(request.type, request.source, info.component)
+                ScreenshotRequest.Builder(request.type, request.source)
+                        .setTopComponent(info.component).build()
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b21a485..6d87922 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -416,10 +416,11 @@
         }
 
         boolean showFlash = false;
-        if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+        if (screenshotScreenBounds == null
+                || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
             showFlash = true;
             visibleInsets = Insets.NONE;
-            screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
+            screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
         }
         mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
@@ -553,6 +554,10 @@
             Log.d(TAG, "adding OnComputeInternalInsetsListener");
         }
         mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
     }
 
     /**
@@ -634,6 +639,7 @@
 
         // The window is focusable by default
         setWindowFocusable(true);
+        mScreenshotView.requestFocus();
 
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
@@ -691,10 +697,7 @@
                     mContext.getDrawable(R.drawable.overlay_badge_background), owner));
         }
         mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "setContentView: " + mScreenshotView);
-        }
-        setContentView(mScreenshotView);
+
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
                 (v, insets) -> WindowInsets.CONSUMED);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 35e9f3e..7b271a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -54,7 +54,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
@@ -186,8 +186,7 @@
         final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
         RequestCallback callback = new RequestCallbackImpl(replyTo);
 
-        ScreenshotHelper.ScreenshotRequest request =
-                (ScreenshotHelper.ScreenshotRequest) msg.obj;
+        ScreenshotRequest request = (ScreenshotRequest) msg.obj;
 
         handleRequest(request, onSaved, callback);
         return true;
@@ -195,7 +194,7 @@
 
     @MainThread
     @VisibleForTesting
-    void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+    void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
             RequestCallback callback) {
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
@@ -226,7 +225,7 @@
                 (r) -> dispatchToController(r, onSaved, callback));
     }
 
-    private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+    private void dispatchToController(ScreenshotRequest request,
             Consumer<Uri> uriConsumer, RequestCallback callback) {
 
         ComponentName topComponent = request.getTopComponent();
@@ -244,8 +243,7 @@
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
                 }
-                Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
-                        request.getBitmapBundle());
+                Bitmap screenshot = request.getBitmap();
                 Rect screenBounds = request.getBoundsInScreen();
                 Insets insets = request.getInsets();
                 int taskId = request.getTaskId();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 5011227..b3d31f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -69,7 +69,8 @@
         }
         return ConstraintsChanges(
             qqsConstraintsChanges = change,
-            qsConstraintsChanges = change
+            qsConstraintsChanges = change,
+            largeScreenConstraintsChanges = change,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index e406be1..8867637 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -113,7 +113,7 @@
             QQS_HEADER_CONSTRAINT -> "QQS Header"
             QS_HEADER_CONSTRAINT -> "QS Header"
             LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
-            else -> "Unknown state"
+            else -> "Unknown state $this"
         }
     }
 
@@ -296,6 +296,9 @@
 
     override fun onViewAttached() {
         privacyIconsController.chipVisibilityListener = chipVisibilityListener
+        updateVisibility()
+        updateTransition()
+
         if (header is MotionLayout) {
             header.setOnApplyWindowInsetsListener(insetListener)
             clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
@@ -308,9 +311,6 @@
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
         demoModeController.addCallback(demoModeReceiver)
-
-        updateVisibility()
-        updateTransition()
     }
 
     override fun onViewDetached() {
@@ -436,15 +436,14 @@
         header as MotionLayout
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
-            header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
+            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
-            updatePosition()
-            updateScrollY()
         }
+        header.jumpToState(header.startState)
+        updatePosition()
+        updateScrollY()
     }
 
     private fun updatePosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fd31e49..bf4a768 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -450,6 +450,7 @@
     private float mDownY;
     private int mDisplayTopInset = 0; // in pixels
     private int mDisplayRightInset = 0; // in pixels
+    private int mDisplayLeftInset = 0; // in pixels
     private int mLargeScreenShadeHeaderHeight;
     private int mSplitShadeNotificationsScrimMarginBottom;
 
@@ -3011,7 +3012,7 @@
             // left bounds can ignore insets, it should always reach the edge of the screen
             return 0;
         } else {
-            return mNotificationStackScrollLayoutController.getLeft();
+            return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset;
         }
     }
 
@@ -3019,7 +3020,7 @@
         if (mIsFullWidth) {
             return mView.getRight() + mDisplayRightInset;
         } else {
-            return mNotificationStackScrollLayoutController.getRight();
+            return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset;
         }
     }
 
@@ -3143,8 +3144,8 @@
 
         // Convert global clipping coordinates to local ones,
         // relative to NotificationStackScrollLayout
-        int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
-        int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
+        int nsslLeft = calculateNsslLeft(left);
+        int nsslRight = calculateNsslRight(right);
         int nsslTop = getNotificationsClippingTopBounds(top);
         int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
         int bottomRadius = mSplitShadeEnabled ? radius : 0;
@@ -3153,6 +3154,22 @@
                 nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
     }
 
+    private int calculateNsslLeft(int nsslLeftAbsolute) {
+        int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return left;
+        }
+        return left - mDisplayLeftInset;
+    }
+
+    private int calculateNsslRight(int nsslRightAbsolute) {
+        int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return right;
+        }
+        return right - mDisplayLeftInset;
+    }
+
     private int getNotificationsClippingTopBounds(int qsTop) {
         if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
             // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
@@ -4550,6 +4567,7 @@
         ipw.print("mDownY="); ipw.println(mDownY);
         ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
         ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+        ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
         ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
         ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
         ipw.println(mSplitShadeNotificationsScrimMarginBottom);
@@ -5927,6 +5945,7 @@
         Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
         mDisplayTopInset = combinedInsets.top;
         mDisplayRightInset = combinedInsets.right;
+        mDisplayLeftInset = combinedInsets.left;
 
         mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 26f8b62..ab2e692 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -19,6 +19,7 @@
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
@@ -52,13 +53,13 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -72,10 +73,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -89,6 +88,7 @@
         Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
+    private static final int MAX_STATE_CHANGES_BUFFER_SIZE = 100;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -108,7 +108,7 @@
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
     private float mScreenBrightnessDoze;
-    private final State mCurrentState = new State();
+    private final NotificationShadeWindowState mCurrentState = new NotificationShadeWindowState();
     private OtherwisedCollapsedListener mListener;
     private ForcePluginOpenListener mForcePluginOpenListener;
     private Consumer<Integer> mScrimsVisibilityListener;
@@ -125,6 +125,9 @@
     private int mDeferWindowLayoutParams;
     private boolean mLastKeyguardRotationAllowed;
 
+    private final NotificationShadeWindowState.Buffer mStateBuffer =
+            new NotificationShadeWindowState.Buffer(MAX_STATE_CHANGES_BUFFER_SIZE);
+
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
@@ -210,8 +213,8 @@
 
     @VisibleForTesting
     void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mCurrentState.mPanelExpanded != isExpanded) {
-            mCurrentState.mPanelExpanded = isExpanded;
+        if (mCurrentState.panelExpanded != isExpanded) {
+            mCurrentState.panelExpanded = isExpanded;
             apply(mCurrentState);
         }
     }
@@ -251,6 +254,7 @@
         mLp.setTitle("NotificationShade");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
         // window manager which disables the transient show behavior.
@@ -296,10 +300,10 @@
         mNotificationShadeView.setSystemUiVisibility(vis);
     }
 
-    private void applyKeyguardFlags(State state) {
-        final boolean keyguardOrAod = state.mKeyguardShowing
-                || (state.mDozing && mDozeParameters.getAlwaysOn());
-        if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque)
+    private void applyKeyguardFlags(NotificationShadeWindowState state) {
+        final boolean keyguardOrAod = state.keyguardShowing
+                || (state.dozing && mDozeParameters.getAlwaysOn());
+        if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
                 || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
             // solid backdrop. Also, show it if we are currently animating between the
@@ -310,15 +314,15 @@
             mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
         }
 
-        if (state.mDozing) {
+        if (state.dozing) {
             mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         }
 
         if (mKeyguardPreferredRefreshRate > 0) {
-            boolean onKeyguard = state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
+            boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
                     && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
                 // both max and min display refresh rate must be set to take effect:
@@ -332,9 +336,9 @@
                     (long) mKeyguardPreferredRefreshRate);
         } else if (mKeyguardMaxRefreshRate > 0) {
             boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
-                    && state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
-            if (state.mDozing || bypassOnKeyguard) {
+                    && state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
+            if (state.dozing || bypassOnKeyguard) {
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
             } else {
                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
@@ -343,7 +347,7 @@
                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
         }
 
-        if (state.mBouncerShowing && !isDebuggable()) {
+        if (state.bouncerShowing && !isDebuggable()) {
             mLpChanged.flags |= LayoutParams.FLAG_SECURE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -354,8 +358,8 @@
         return Build.IS_DEBUGGABLE;
     }
 
-    private void adjustScreenOrientation(State state) {
-        if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+    private void adjustScreenOrientation(NotificationShadeWindowState state) {
+        if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
             if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
             } else {
@@ -366,10 +370,10 @@
         }
     }
 
-    private void applyFocusableFlag(State state) {
-        boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
-        if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+    private void applyFocusableFlag(NotificationShadeWindowState state) {
+        boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive
                 // Make the panel focusable if we're doing the screen off animation, since the light
                 // reveal scrim is drawing in the panel and should consume touch events so that they
                 // don't go to the app behind.
@@ -379,7 +383,7 @@
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
-            if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
+            if (state.keyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
                 mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
             } else {
                 mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -390,19 +394,19 @@
         }
     }
 
-    private void applyForceShowNavigationFlag(State state) {
-        if (state.mPanelExpanded || state.mBouncerShowing
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+    private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
+        if (state.panelExpanded || state.bouncerShowing
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         }
     }
 
-    private void applyVisibility(State state) {
+    private void applyVisibility(NotificationShadeWindowState state) {
         boolean visible = isExpanded(state);
         mLogger.logApplyVisibility(visible);
-        if (state.mForcePluginOpen) {
+        if (state.forcePluginOpen) {
             if (mListener != null) {
                 mListener.setWouldOtherwiseCollapse(visible);
             }
@@ -418,16 +422,16 @@
         }
     }
 
-    private boolean isExpanded(State state) {
-        return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
-                || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
-                || state.mHeadsUpShowing
-                || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0
-                || state.mLaunchingActivity;
+    private boolean isExpanded(NotificationShadeWindowState state) {
+        return !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
+                || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
+                || state.headsUpNotificationShowing
+                || state.scrimsVisibility != ScrimController.TRANSPARENT)
+                || state.backgroundBlurRadius > 0
+                || state.launchingActivityFromNotification;
     }
 
-    private void applyFitsSystemWindows(State state) {
+    private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
         if (mNotificationShadeView != null
                 && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
@@ -436,21 +440,21 @@
         }
     }
 
-    private void applyUserActivityTimeout(State state) {
+    private void applyUserActivityTimeout(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded) {
-            mLpChanged.userActivityTimeout = state.mBouncerShowing
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded) {
+            mLpChanged.userActivityTimeout = state.bouncerShowing
                     ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
         } else {
             mLpChanged.userActivityTimeout = -1;
         }
     }
 
-    private void applyInputFeatures(State state) {
+    private void applyInputFeatures(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded && !state.mForceUserActivity) {
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded && !state.forceUserActivity) {
             mLpChanged.inputFeatures |=
                     LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
         } else {
@@ -459,7 +463,7 @@
         }
     }
 
-    private void applyStatusBarColorSpaceAgnosticFlag(State state) {
+    private void applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state) {
         if (!isExpanded(state)) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
         } else {
@@ -485,8 +489,8 @@
         applyWindowLayoutParams();
     }
 
-    private void apply(State state) {
-        mLogger.logNewState(state);
+    private void apply(NotificationShadeWindowState state) {
+        logState(state);
         applyKeyguardFlags(state);
         applyFocusableFlag(state);
         applyForceShowNavigationFlag(state);
@@ -515,6 +519,38 @@
         notifyStateChangedCallbacks();
     }
 
+    private void logState(NotificationShadeWindowState state) {
+        mStateBuffer.insert(
+                state.keyguardShowing,
+                state.keyguardOccluded,
+                state.keyguardNeedsInput,
+                state.panelVisible,
+                state.panelExpanded,
+                state.notificationShadeFocusable,
+                state.bouncerShowing,
+                state.keyguardFadingAway,
+                state.keyguardGoingAway,
+                state.qsExpanded,
+                state.headsUpNotificationShowing,
+                state.lightRevealScrimOpaque,
+                state.forceWindowCollapsed,
+                state.forceDozeBrightness,
+                state.forceUserActivity,
+                state.launchingActivityFromNotification,
+                state.mediaBackdropShowing,
+                state.wallpaperSupportsAmbientMode,
+                state.windowNotTouchable,
+                state.componentsForcingTopUi,
+                state.forceOpenTokens,
+                state.statusBarState,
+                state.remoteInputActive,
+                state.forcePluginOpen,
+                state.dozing,
+                state.scrimsVisibility,
+                state.backgroundBlurRadius
+        );
+    }
+
     @Override
     public void notifyStateChangedCallbacks() {
         // Copy callbacks to separate ArrayList to avoid concurrent modification
@@ -523,36 +559,36 @@
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
         for (StatusBarWindowCallback cb : activeCallbacks) {
-            cb.onStateChanged(mCurrentState.mKeyguardShowing,
-                    mCurrentState.mKeyguardOccluded,
-                    mCurrentState.mBouncerShowing,
-                    mCurrentState.mDozing,
-                    mCurrentState.mPanelExpanded);
+            cb.onStateChanged(mCurrentState.keyguardShowing,
+                    mCurrentState.keyguardOccluded,
+                    mCurrentState.bouncerShowing,
+                    mCurrentState.dozing,
+                    mCurrentState.panelExpanded);
         }
     }
 
-    private void applyModalFlag(State state) {
-        if (state.mHeadsUpShowing) {
+    private void applyModalFlag(NotificationShadeWindowState state) {
+        if (state.headsUpNotificationShowing) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
         }
     }
 
-    private void applyBrightness(State state) {
-        if (state.mForceDozeBrightness) {
+    private void applyBrightness(NotificationShadeWindowState state) {
+        if (state.forceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
             mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
         }
     }
 
-    private void applyHasTopUi(State state) {
-        mHasTopUiChanged = !state.mComponentsForcingTopUi.isEmpty() || isExpanded(state);
+    private void applyHasTopUi(NotificationShadeWindowState state) {
+        mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
     }
 
-    private void applyNotTouchable(State state) {
-        if (state.mNotTouchable) {
+    private void applyNotTouchable(NotificationShadeWindowState state) {
+        if (state.windowNotTouchable) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -574,88 +610,88 @@
 
     @Override
     public void setKeyguardShowing(boolean showing) {
-        mCurrentState.mKeyguardShowing = showing;
+        mCurrentState.keyguardShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardOccluded(boolean occluded) {
-        mCurrentState.mKeyguardOccluded = occluded;
+        mCurrentState.keyguardOccluded = occluded;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardNeedsInput(boolean needsInput) {
-        mCurrentState.mKeyguardNeedsInput = needsInput;
+        mCurrentState.keyguardNeedsInput = needsInput;
         apply(mCurrentState);
     }
 
     @Override
     public void setPanelVisible(boolean visible) {
-        if (mCurrentState.mPanelVisible == visible
-                && mCurrentState.mNotificationShadeFocusable == visible) {
+        if (mCurrentState.panelVisible == visible
+                && mCurrentState.notificationShadeFocusable == visible) {
             return;
         }
         mLogger.logShadeVisibleAndFocusable(visible);
-        mCurrentState.mPanelVisible = visible;
-        mCurrentState.mNotificationShadeFocusable = visible;
+        mCurrentState.panelVisible = visible;
+        mCurrentState.notificationShadeFocusable = visible;
         apply(mCurrentState);
     }
 
     @Override
     public void setNotificationShadeFocusable(boolean focusable) {
         mLogger.logShadeFocusable(focusable);
-        mCurrentState.mNotificationShadeFocusable = focusable;
+        mCurrentState.notificationShadeFocusable = focusable;
         apply(mCurrentState);
     }
 
     @Override
     public void setBouncerShowing(boolean showing) {
-        mCurrentState.mBouncerShowing = showing;
+        mCurrentState.bouncerShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setBackdropShowing(boolean showing) {
-        mCurrentState.mBackdropShowing = showing;
+        mCurrentState.mediaBackdropShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
-        mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
+        mCurrentState.keyguardFadingAway = keyguardFadingAway;
         apply(mCurrentState);
     }
 
     private void onQsExpansionChanged(Boolean expanded) {
-        mCurrentState.mQsExpanded = expanded;
+        mCurrentState.qsExpanded = expanded;
         apply(mCurrentState);
     }
 
     @Override
     public void setForceUserActivity(boolean forceUserActivity) {
-        mCurrentState.mForceUserActivity = forceUserActivity;
+        mCurrentState.forceUserActivity = forceUserActivity;
         apply(mCurrentState);
     }
 
     @Override
     public void setLaunchingActivity(boolean launching) {
-        mCurrentState.mLaunchingActivity = launching;
+        mCurrentState.launchingActivityFromNotification = launching;
         apply(mCurrentState);
     }
 
     @Override
     public boolean isLaunchingActivity() {
-        return mCurrentState.mLaunchingActivity;
+        return mCurrentState.launchingActivityFromNotification;
     }
 
     @Override
     public void setScrimsVisibility(int scrimsVisibility) {
-        if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+        if (scrimsVisibility == mCurrentState.scrimsVisibility) {
             return;
         }
         boolean wasExpanded = isExpanded(mCurrentState);
-        mCurrentState.mScrimsVisibility = scrimsVisibility;
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         if (wasExpanded != isExpanded(mCurrentState)) {
             apply(mCurrentState);
         }
@@ -669,31 +705,31 @@
      */
     @Override
     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
-        if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
+        if (mCurrentState.backgroundBlurRadius == backgroundBlurRadius) {
             return;
         }
-        mCurrentState.mBackgroundBlurRadius = backgroundBlurRadius;
+        mCurrentState.backgroundBlurRadius = backgroundBlurRadius;
         apply(mCurrentState);
     }
 
     @Override
     public void setHeadsUpShowing(boolean showing) {
-        mCurrentState.mHeadsUpShowing = showing;
+        mCurrentState.headsUpNotificationShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setLightRevealScrimOpaque(boolean opaque) {
-        if (mCurrentState.mLightRevealScrimOpaque == opaque) {
+        if (mCurrentState.lightRevealScrimOpaque == opaque) {
             return;
         }
-        mCurrentState.mLightRevealScrimOpaque = opaque;
+        mCurrentState.lightRevealScrimOpaque = opaque;
         apply(mCurrentState);
     }
 
     @Override
     public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
+        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
         apply(mCurrentState);
     }
 
@@ -701,7 +737,7 @@
      * @param state The {@link StatusBarStateController} of the status bar.
      */
     private void setStatusBarState(int state) {
-        mCurrentState.mStatusBarState = state;
+        mCurrentState.statusBarState = state;
         apply(mCurrentState);
     }
 
@@ -712,13 +748,13 @@
      */
     @Override
     public void setForceWindowCollapsed(boolean force) {
-        mCurrentState.mForceCollapsed = force;
+        mCurrentState.forceWindowCollapsed = force;
         apply(mCurrentState);
     }
 
     @Override
     public void onRemoteInputActive(boolean remoteInputActive) {
-        mCurrentState.mRemoteInputActive = remoteInputActive;
+        mCurrentState.remoteInputActive = remoteInputActive;
         apply(mCurrentState);
     }
 
@@ -728,32 +764,32 @@
      */
     @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+        if (mCurrentState.forceDozeBrightness == forceDozeBrightness) {
             return;
         }
-        mCurrentState.mForceDozeBrightness = forceDozeBrightness;
+        mCurrentState.forceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
 
     @Override
     public void setDozing(boolean dozing) {
-        mCurrentState.mDozing = dozing;
+        mCurrentState.dozing = dozing;
         apply(mCurrentState);
     }
 
     @Override
     public void setForcePluginOpen(boolean forceOpen, Object token) {
         if (forceOpen) {
-            mCurrentState.mForceOpenTokens.add(token);
+            mCurrentState.forceOpenTokens.add(token);
         } else {
-            mCurrentState.mForceOpenTokens.remove(token);
+            mCurrentState.forceOpenTokens.remove(token);
         }
-        final boolean previousForceOpenState = mCurrentState.mForcePluginOpen;
-        mCurrentState.mForcePluginOpen = !mCurrentState.mForceOpenTokens.isEmpty();
-        if (previousForceOpenState != mCurrentState.mForcePluginOpen) {
+        final boolean previousForceOpenState = mCurrentState.forcePluginOpen;
+        mCurrentState.forcePluginOpen = !mCurrentState.forceOpenTokens.isEmpty();
+        if (previousForceOpenState != mCurrentState.forcePluginOpen) {
             apply(mCurrentState);
             if (mForcePluginOpenListener != null) {
-                mForcePluginOpenListener.onChange(mCurrentState.mForcePluginOpen);
+                mForcePluginOpenListener.onChange(mCurrentState.forcePluginOpen);
             }
         }
     }
@@ -763,12 +799,12 @@
      */
     @Override
     public boolean getForcePluginOpen() {
-        return mCurrentState.mForcePluginOpen;
+        return mCurrentState.forcePluginOpen;
     }
 
     @Override
     public void setNotTouchable(boolean notTouchable) {
-        mCurrentState.mNotTouchable = notTouchable;
+        mCurrentState.windowNotTouchable = notTouchable;
         apply(mCurrentState);
     }
 
@@ -777,7 +813,7 @@
      */
     @Override
     public boolean getPanelExpanded() {
-        return mCurrentState.mPanelExpanded;
+        return mCurrentState.panelExpanded;
     }
 
     @Override
@@ -800,11 +836,16 @@
         if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
             mNotificationShadeView.getViewRootImpl().dump("  ", pw);
         }
+        new DumpsysTableLogger(
+                TAG,
+                NotificationShadeWindowState.TABLE_HEADERS,
+                mStateBuffer.toList()
+        ).printTableData(pw);
     }
 
     @Override
     public boolean isShowingWallpaper() {
-        return !mCurrentState.mBackdropShowing;
+        return !mCurrentState.mediaBackdropShowing;
     }
 
     @Override
@@ -834,7 +875,7 @@
      */
     @Override
     public void setKeyguardGoingAway(boolean goingAway) {
-        mCurrentState.mKeyguardGoingAway = goingAway;
+        mCurrentState.keyguardGoingAway = goingAway;
         apply(mCurrentState);
     }
 
@@ -846,87 +887,13 @@
     @Override
     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
         if (requestTopUi) {
-            mCurrentState.mComponentsForcingTopUi.add(componentTag);
+            mCurrentState.componentsForcingTopUi.add(componentTag);
         } else {
-            mCurrentState.mComponentsForcingTopUi.remove(componentTag);
+            mCurrentState.componentsForcingTopUi.remove(componentTag);
         }
         apply(mCurrentState);
     }
 
-    private static class State {
-        boolean mKeyguardShowing;
-        boolean mKeyguardOccluded;
-        boolean mKeyguardNeedsInput;
-        boolean mPanelVisible;
-        boolean mPanelExpanded;
-        boolean mNotificationShadeFocusable;
-        boolean mBouncerShowing;
-        boolean mKeyguardFadingAway;
-        boolean mKeyguardGoingAway;
-        boolean mQsExpanded;
-        boolean mHeadsUpShowing;
-        boolean mLightRevealScrimOpaque;
-        boolean mForceCollapsed;
-        boolean mForceDozeBrightness;
-        boolean mForceUserActivity;
-        boolean mLaunchingActivity;
-        boolean mBackdropShowing;
-        boolean mWallpaperSupportsAmbientMode;
-        boolean mNotTouchable;
-        Set<String> mComponentsForcingTopUi = new HashSet<>();
-        Set<Object> mForceOpenTokens = new HashSet<>();
-
-        /**
-         * The status bar state from {@link CentralSurfaces}.
-         */
-        int mStatusBarState;
-
-        boolean mRemoteInputActive;
-        boolean mForcePluginOpen;
-        boolean mDozing;
-        int mScrimsVisibility;
-        int mBackgroundBlurRadius;
-
-        private boolean isKeyguardShowingAndNotOccluded() {
-            return mKeyguardShowing && !mKeyguardOccluded;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder()
-                    .append("State{")
-                    .append("  mKeyguardShowing=").append(mKeyguardShowing)
-                    .append(", mKeyguardOccluded=").append(mKeyguardOccluded)
-                    .append(", mKeyguardNeedsInput=").append(mKeyguardNeedsInput)
-                    .append(", mPanelVisible=").append(mPanelVisible)
-                    .append(", mPanelExpanded=").append(mPanelExpanded)
-                    .append(", mNotificationShadeFocusable=").append(mNotificationShadeFocusable)
-                    .append(", mBouncerShowing=").append(mBouncerShowing)
-                    .append(", mKeyguardFadingAway=").append(mKeyguardFadingAway)
-                    .append(", mKeyguardGoingAway=").append(mKeyguardGoingAway)
-                    .append(", mQsExpanded=").append(mQsExpanded)
-                    .append(", mHeadsUpShowing=").append(mHeadsUpShowing)
-                    .append(", mLightRevealScrimOpaque=").append(mLightRevealScrimOpaque)
-                    .append(", mForceCollapsed=").append(mForceCollapsed)
-                    .append(", mForceDozeBrightness=").append(mForceDozeBrightness)
-                    .append(", mForceUserActivity=").append(mForceUserActivity)
-                    .append(", mLaunchingActivity=").append(mLaunchingActivity)
-                    .append(", mBackdropShowing=").append(mBackdropShowing)
-                    .append(", mWallpaperSupportsAmbientMode=")
-                    .append(mWallpaperSupportsAmbientMode)
-                    .append(", mNotTouchable=").append(mNotTouchable)
-                    .append(", mComponentsForcingTopUi=").append(mComponentsForcingTopUi)
-                    .append(", mForceOpenTokens=").append(mForceOpenTokens)
-                    .append(", mStatusBarState=").append(mStatusBarState)
-                    .append(", mRemoteInputActive=").append(mRemoteInputActive)
-                    .append(", mForcePluginOpen=").append(mForcePluginOpen)
-                    .append(", mDozing=").append(mDozing)
-                    .append(", mScrimsVisibility=").append(mScrimsVisibility)
-                    .append(", mBackgroundBlurRadius=").append(mBackgroundBlurRadius)
-                    .append('}').toString();
-        }
-    }
-
     private final StateListener mStateListener = new StateListener() {
         @Override
         public void onStateChanged(int newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
new file mode 100644
index 0000000..736404aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.shade
+
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.shade.NotificationShadeWindowState.Buffer
+import com.android.systemui.statusbar.StatusBarState
+
+/**
+ * Represents state of shade window, used by [NotificationShadeWindowControllerImpl].
+ * Contains nested class [Buffer] for pretty table logging in bug reports.
+ */
+class NotificationShadeWindowState(
+    @JvmField var keyguardShowing: Boolean = false,
+    @JvmField var keyguardOccluded: Boolean = false,
+    @JvmField var keyguardNeedsInput: Boolean = false,
+    @JvmField var panelVisible: Boolean = false,
+    /** shade panel is expanded (expansion fraction > 0) */
+    @JvmField var panelExpanded: Boolean = false,
+    @JvmField var notificationShadeFocusable: Boolean = false,
+    @JvmField var bouncerShowing: Boolean = false,
+    @JvmField var keyguardFadingAway: Boolean = false,
+    @JvmField var keyguardGoingAway: Boolean = false,
+    @JvmField var qsExpanded: Boolean = false,
+    @JvmField var headsUpNotificationShowing: Boolean = false,
+    @JvmField var lightRevealScrimOpaque: Boolean = false,
+    @JvmField var forceWindowCollapsed: Boolean = false,
+    @JvmField var forceDozeBrightness: Boolean = false,
+    // TODO: forceUserActivity seems to be unused, delete?
+    @JvmField var forceUserActivity: Boolean = false,
+    @JvmField var launchingActivityFromNotification: Boolean = false,
+    @JvmField var mediaBackdropShowing: Boolean = false,
+    @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
+    @JvmField var windowNotTouchable: Boolean = false,
+    @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
+    @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
+    /** one of [StatusBarState] */
+    @JvmField var statusBarState: Int = 0,
+    @JvmField var remoteInputActive: Boolean = false,
+    @JvmField var forcePluginOpen: Boolean = false,
+    @JvmField var dozing: Boolean = false,
+    @JvmField var scrimsVisibility: Int = 0,
+    @JvmField var backgroundBlurRadius: Int = 0,
+) {
+
+    fun isKeyguardShowingAndNotOccluded(): Boolean {
+        return keyguardShowing && !keyguardOccluded
+    }
+
+    /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
+    val asStringList: List<String> by lazy {
+        listOf(
+            keyguardShowing.toString(),
+            keyguardOccluded.toString(),
+            keyguardNeedsInput.toString(),
+            panelVisible.toString(),
+            panelExpanded.toString(),
+            notificationShadeFocusable.toString(),
+            bouncerShowing.toString(),
+            keyguardFadingAway.toString(),
+            keyguardGoingAway.toString(),
+            qsExpanded.toString(),
+            headsUpNotificationShowing.toString(),
+            lightRevealScrimOpaque.toString(),
+            forceWindowCollapsed.toString(),
+            forceDozeBrightness.toString(),
+            forceUserActivity.toString(),
+            launchingActivityFromNotification.toString(),
+            mediaBackdropShowing.toString(),
+            wallpaperSupportsAmbientMode.toString(),
+            windowNotTouchable.toString(),
+            componentsForcingTopUi.toString(),
+            forceOpenTokens.toString(),
+            StatusBarState.toString(statusBarState),
+            remoteInputActive.toString(),
+            forcePluginOpen.toString(),
+            dozing.toString(),
+            scrimsVisibility.toString(),
+            backgroundBlurRadius.toString()
+        )
+    }
+
+    /**
+     * [RingBuffer] to store [NotificationShadeWindowState]. After the buffer is full, it will
+     * recycle old events.
+     */
+    class Buffer(capacity: Int) {
+
+        private val buffer = RingBuffer(capacity) { NotificationShadeWindowState() }
+
+        /** Insert a new element in the buffer. */
+        fun insert(
+            keyguardShowing: Boolean,
+            keyguardOccluded: Boolean,
+            keyguardNeedsInput: Boolean,
+            panelVisible: Boolean,
+            panelExpanded: Boolean,
+            notificationShadeFocusable: Boolean,
+            bouncerShowing: Boolean,
+            keyguardFadingAway: Boolean,
+            keyguardGoingAway: Boolean,
+            qsExpanded: Boolean,
+            headsUpShowing: Boolean,
+            lightRevealScrimOpaque: Boolean,
+            forceCollapsed: Boolean,
+            forceDozeBrightness: Boolean,
+            forceUserActivity: Boolean,
+            launchingActivity: Boolean,
+            backdropShowing: Boolean,
+            wallpaperSupportsAmbientMode: Boolean,
+            notTouchable: Boolean,
+            componentsForcingTopUi: MutableSet<String>,
+            forceOpenTokens: MutableSet<Any>,
+            statusBarState: Int,
+            remoteInputActive: Boolean,
+            forcePluginOpen: Boolean,
+            dozing: Boolean,
+            scrimsVisibility: Int,
+            backgroundBlurRadius: Int,
+        ) {
+            buffer.advance().apply {
+                this.keyguardShowing = keyguardShowing
+                this.keyguardOccluded = keyguardOccluded
+                this.keyguardNeedsInput = keyguardNeedsInput
+                this.panelVisible = panelVisible
+                this.panelExpanded = panelExpanded
+                this.notificationShadeFocusable = notificationShadeFocusable
+                this.bouncerShowing = bouncerShowing
+                this.keyguardFadingAway = keyguardFadingAway
+                this.keyguardGoingAway = keyguardGoingAway
+                this.qsExpanded = qsExpanded
+                this.headsUpNotificationShowing = headsUpShowing
+                this.lightRevealScrimOpaque = lightRevealScrimOpaque
+                this.forceWindowCollapsed = forceCollapsed
+                this.forceDozeBrightness = forceDozeBrightness
+                this.forceUserActivity = forceUserActivity
+                this.launchingActivityFromNotification = launchingActivity
+                this.mediaBackdropShowing = backdropShowing
+                this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
+                this.windowNotTouchable = notTouchable
+                this.componentsForcingTopUi.clear()
+                this.componentsForcingTopUi.addAll(componentsForcingTopUi)
+                this.forceOpenTokens.clear()
+                this.forceOpenTokens.addAll(forceOpenTokens)
+                this.statusBarState = statusBarState
+                this.remoteInputActive = remoteInputActive
+                this.forcePluginOpen = forcePluginOpen
+                this.dozing = dozing
+                this.scrimsVisibility = scrimsVisibility
+                this.backgroundBlurRadius = backgroundBlurRadius
+            }
+        }
+
+        /**
+         * Returns the content of the buffer (sorted from latest to newest).
+         *
+         * @see [NotificationShadeWindowState.asStringList]
+         */
+        fun toList(): List<Row> {
+            return buffer.asSequence().map { it.asStringList }.toList()
+        }
+    }
+
+    companion object {
+        /** Headers for dumping a table using [DumpsysTableLogger]. */
+        @JvmField
+        val TABLE_HEADERS =
+            listOf(
+                "keyguardShowing",
+                "keyguardOccluded",
+                "keyguardNeedsInput",
+                "panelVisible",
+                "panelExpanded",
+                "notificationShadeFocusable",
+                "bouncerShowing",
+                "keyguardFadingAway",
+                "keyguardGoingAway",
+                "qsExpanded",
+                "headsUpShowing",
+                "lightRevealScrimOpaque",
+                "forceCollapsed",
+                "forceDozeBrightness",
+                "forceUserActivity",
+                "launchingActivity",
+                "backdropShowing",
+                "wallpaperSupportsAmbientMode",
+                "notTouchable",
+                "componentsForcingTopUi",
+                "forceOpenTokens",
+                "statusBarState",
+                "remoteInputActive",
+                "forcePluginOpen",
+                "dozing",
+                "scrimsVisibility",
+                "backgroundBlurRadius"
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6e4ed7b0..7556750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,6 +53,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
@@ -1270,7 +1271,8 @@
     public void showMediaOutputSwitcher(String packageName) {
         int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
-            throw new SecurityException("Call only allowed from system server.");
+            Slog.e(TAG, "Call only allowed from system server.");
+            return;
         }
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
new file mode 100644
index 0000000..a797d4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.statusbar;
+
+import android.content.BroadcastReceiver;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Module for {@link com.android.systemui.KeyboardShortcutsReceiver}.
+ */
+@Module
+public abstract class KeyboardShortcutsModule {
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardShortcutsReceiver.class)
+    public abstract BroadcastReceiver bindKeyboardShortcutsReceiver(
+            KeyboardShortcutsReceiver broadcastReceiver);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index fbe88df..7addc8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -24,6 +24,7 @@
 import android.graphics.Point;
 import android.util.AttributeSet;
 import android.util.MathUtils;
+import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
@@ -492,12 +493,9 @@
         if (animationListener != null) {
             mAppearAnimator.addListener(animationListener);
         }
-        if (delay > 0) {
-            // we need to apply the initial state already to avoid drawn frames in the wrong state
-            updateAppearAnimationAlpha();
-            updateAppearRect();
-            mAppearAnimator.setStartDelay(delay);
-        }
+        // we need to apply the initial state already to avoid drawn frames in the wrong state
+        updateAppearAnimationAlpha();
+        updateAppearRect();
         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mWasCancelled;
 
@@ -528,7 +526,20 @@
                 mWasCancelled = true;
             }
         });
-        mAppearAnimator.start();
+
+        // Cache the original animator so we can check if the animation should be started in the
+        // Choreographer callback. It's possible that the original animator (mAppearAnimator) is
+        // replaced with a new value before the callback is called.
+        ValueAnimator cachedAnimator = mAppearAnimator;
+        // Even when delay=0, starting the animation on the next frame is necessary to avoid jank.
+        // Not doing so will increase the chances our Animator will be forced to skip a value of
+        // the animation's progression, causing stutter.
+        Choreographer.getInstance().postFrameCallbackDelayed(
+                frameTimeNanos -> {
+                    if (mAppearAnimator == cachedAnimator) {
+                        mAppearAnimator.start();
+                    }
+                }, delay);
     }
 
     private int getCujType(boolean isAppearing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f50aef..9275e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -79,6 +79,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -177,6 +179,7 @@
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private Optional<BubblesManager> mBubblesManagerOptional;
     private MetricsLogger mMetricsLogger;
+    private FeatureFlags mFeatureFlags;
     private int mIconTransformContentShift;
     private int mMaxHeadsUpHeightBeforeN;
     private int mMaxHeadsUpHeightBeforeP;
@@ -277,7 +280,7 @@
     private boolean mChildIsExpanding;
 
     private boolean mJustClicked;
-    private boolean mIconAnimationRunning;
+    private boolean mAnimationRunning;
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
@@ -451,10 +454,26 @@
         return mPublicLayout;
     }
 
-    public void setIconAnimationRunning(boolean running) {
-        for (NotificationContentView l : mLayouts) {
-            setIconAnimationRunning(running, l);
+    /**
+     * Sets animations running in the layouts of this row, including public, private, and children.
+     * @param running whether the animations should be started running or stopped.
+     */
+    public void setAnimationRunning(boolean running) {
+        // Sets animations running in the private/public layouts.
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
+            for (NotificationContentView l : mLayouts) {
+                if (l != null) {
+                    l.setContentAnimationRunning(running);
+                    setIconAnimationRunning(running, l);
+                }
+            }
+        } else {
+            for (NotificationContentView l : mLayouts) {
+                setIconAnimationRunning(running, l);
+            }
         }
+        // For groups summaries with children, we want to set the children containers
+        // animating as well.
         if (mIsSummaryWithChildren) {
             NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
             if (viewWrapper != null) {
@@ -468,12 +487,18 @@
                     mChildrenContainer.getAttachedChildren();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow child = notificationChildren.get(i);
-                child.setIconAnimationRunning(running);
+                child.setAnimationRunning(running);
             }
         }
-        mIconAnimationRunning = running;
+        mAnimationRunning = running;
     }
 
+    /**
+     * Starts or stops animations of the icons in all potential content views (regardless of
+     * whether they're contracted, expanded, etc).
+     *
+     * @param running whether to start or stop the icon's animation.
+     */
     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
         if (layout != null) {
             View contractedChild = layout.getContractedChild();
@@ -485,16 +510,29 @@
         }
     }
 
+    /**
+     * Starts or stops animations of the icon in the provided view's icon and right icon.
+     *
+     * @param running whether to start or stop the icon's animation.
+     * @param child   the view with the icon to start or stop.
+     */
     private void setIconAnimationRunningForChild(boolean running, View child) {
         if (child != null) {
             ImageView icon = child.findViewById(com.android.internal.R.id.icon);
-            setIconRunning(icon, running);
+            setImageViewAnimationRunning(icon, running);
             ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
-            setIconRunning(rightIcon, running);
+            setImageViewAnimationRunning(rightIcon, running);
         }
     }
 
-    private void setIconRunning(ImageView imageView, boolean running) {
+    /**
+     * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
+     * AnimatedVectorDrawable.
+     *
+     * @param imageView the image view on which to start/stop animation.
+     * @param running   whether to start or stop the view's animation.
+     */
+    private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
         if (imageView != null) {
             Drawable drawable = imageView.getDrawable();
             if (drawable instanceof AnimationDrawable) {
@@ -561,8 +599,8 @@
             mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
             mChildrenContainer.onNotificationUpdated();
         }
-        if (mIconAnimationRunning) {
-            setIconAnimationRunning(true);
+        if (mAnimationRunning) {
+            setAnimationRunning(true);
         }
         if (mLastChronometerRunning) {
             setChronometerRunning(true);
@@ -1038,7 +1076,7 @@
             notifyHeightChanged(false /* needsAnimation */);
         }
         if (pinned) {
-            setIconAnimationRunning(true);
+            setAnimationRunning(true);
             mExpandedWhenPinned = false;
         } else if (mExpandedWhenPinned) {
             setUserExpanded(true);
@@ -1627,6 +1665,11 @@
         );
     }
 
+    /**
+     * Constructs an ExpandableNotificationRow.
+     * @param context context passed to image resolver
+     * @param attrs attributes used to initialize parent view
+     */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
@@ -1662,7 +1705,8 @@
             NotificationGutsManager gutsManager,
             MetricsLogger metricsLogger,
             SmartReplyConstants smartReplyConstants,
-            SmartReplyController smartReplyController) {
+            SmartReplyController smartReplyController,
+            FeatureFlags featureFlags) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1697,6 +1741,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mNotificationGutsManager = gutsManager;
         mMetricsLogger = metricsLogger;
+        mFeatureFlags = featureFlags;
     }
 
     private void initDimens() {
@@ -3588,11 +3633,13 @@
     @VisibleForTesting
     protected void setPrivateLayout(NotificationContentView privateLayout) {
         mPrivateLayout = privateLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     @VisibleForTesting
     protected void setPublicLayout(NotificationContentView publicLayout) {
         mPublicLayout = publicLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index d113860..bb92dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -219,7 +219,8 @@
                 mNotificationGutsManager,
                 mMetricsLogger,
                 mSmartReplyConstants,
-                mSmartReplyController
+                mSmartReplyController,
+                mFeatureFlags
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 49dc655..21f4cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,15 +16,22 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -41,6 +48,11 @@
     private String mManageNotificationText;
     private String mManageNotificationHistoryText;
 
+    // Footer label
+    private TextView mSeenNotifsFooterTextView;
+    private @StringRes int mSeenNotifsFilteredText;
+    private int mUnlockIconSize;
+
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -73,10 +85,41 @@
         super.onFinishInflate();
         mClearAllButton = (FooterViewButton) findSecondaryView();
         mManageButton = findViewById(R.id.manage_text);
+        mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
         updateResources();
         updateText();
     }
 
+    public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
+        mSeenNotifsFilteredText = text;
+        if (mSeenNotifsFilteredText != 0) {
+            mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+        } else {
+            mSeenNotifsFooterTextView.setText(null);
+        }
+        Drawable drawable;
+        if (icon == 0) {
+            drawable = null;
+        } else {
+            drawable = getResources().getDrawable(icon);
+            drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
+        }
+        mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
+        updateFooterVisibilityMode();
+    }
+
+    private void updateFooterVisibilityMode() {
+        if (mSeenNotifsFilteredText != 0) {
+            mManageButton.setVisibility(View.GONE);
+            mClearAllButton.setVisibility(View.GONE);
+            mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+        } else {
+            mManageButton.setVisibility(View.VISIBLE);
+            mClearAllButton.setVisibility(View.VISIBLE);
+            mSeenNotifsFooterTextView.setVisibility(View.GONE);
+        }
+    }
+
     public void setManageButtonClickListener(OnClickListener listener) {
         mManageButton.setOnClickListener(listener);
     }
@@ -135,12 +178,19 @@
         mClearAllButton.setTextColor(textColor);
         mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
         mManageButton.setTextColor(textColor);
+        final @ColorInt int labelTextColor =
+                Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
+        mSeenNotifsFooterTextView.setTextColor(labelTextColor);
+        mSeenNotifsFooterTextView.setCompoundDrawableTintList(
+                ColorStateList.valueOf(labelTextColor));
     }
 
     private void updateResources() {
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
+        mUnlockIconSize = getResources()
+                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index e46bf52..4a023c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -184,6 +184,8 @@
     private boolean mRemoteInputVisible;
     private int mUnrestrictedContentHeight;
 
+    private boolean mContentAnimating;
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext());
@@ -2129,8 +2131,49 @@
         return false;
     }
 
+    /**
+     * Starts and stops animations in the underlying views.
+     * Avoids restarting the animations by checking whether they're already running first.
+     * Return value is used for testing.
+     *
+     * @param running whether to start animations running, or stop them.
+     * @return true if the state of animations changed.
+     */
+    public boolean setContentAnimationRunning(boolean running) {
+        boolean stateChangeRequired = (running != mContentAnimating);
+        if (stateChangeRequired) {
+            // Starts or stops the animations in the potential views.
+            if (mContractedWrapper != null) {
+                mContractedWrapper.setAnimationsRunning(running);
+            }
+            if (mExpandedWrapper != null) {
+                mExpandedWrapper.setAnimationsRunning(running);
+            }
+            if (mHeadsUpWrapper != null) {
+                mHeadsUpWrapper.setAnimationsRunning(running);
+            }
+            // Updates the state tracker.
+            mContentAnimating = running;
+            return true;
+        }
+        return false;
+    }
+
     private static class RemoteInputViewData {
         @Nullable RemoteInputView mView;
         @Nullable RemoteInputViewController mController;
     }
+
+    @VisibleForTesting
+    protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
+        mContractedWrapper = contractedWrapper;
+    }
+    @VisibleForTesting
+    protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
+        mExpandedWrapper = expandedWrapper;
+    }
+    @VisibleForTesting
+    protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
+        mHeadsUpWrapper = headsUpWrapper;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 8732696..175ba15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -18,11 +18,15 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.view.View;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.statusbar.notification.ImageTransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -31,6 +35,8 @@
  */
 public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
+    private BigPictureNotificationImageView mImageView;
+
     protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -39,9 +45,14 @@
     @Override
     public void onContentUpdated(ExpandableNotificationRow row) {
         super.onContentUpdated(row);
+        resolveViews();
         updateImageTag(row.getEntry().getSbn());
     }
 
+    private void resolveViews() {
+        mImageView = mView.findViewById(R.id.big_picture);
+    }
+
     private void updateImageTag(StatusBarNotification sbn) {
         final Bundle extras = sbn.getNotification().extras;
         Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class);
@@ -54,4 +65,25 @@
             mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
         }
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this BigPicture Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mImageView == null) {
+            return;
+        }
+        Drawable d = mImageView.getDrawable();
+        if (d instanceof AnimatedImageDrawable) {
+            AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+            if (running) {
+                animatedImageDrawable.start();
+            } else {
+                animatedImageDrawable.stop();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index e136055..10753f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,16 +17,20 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.content.Context
+import android.graphics.drawable.AnimatedImageDrawable
 import android.view.View
 import android.view.ViewGroup
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
 import com.android.internal.widget.MessagingLinearLayout
 import com.android.systemui.R
 import com.android.systemui.statusbar.notification.NotificationFadeAware
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
+import com.android.systemui.util.children
 
 /**
  * Wraps a notification containing a conversation template
@@ -49,6 +53,7 @@
     private lateinit var expandBtn: View
     private lateinit var expandBtnContainer: View
     private lateinit var imageMessageContainer: ViewGroup
+    private lateinit var messageContainers: ArrayList<MessagingGroup>
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
     private lateinit var importanceRing: View
@@ -60,6 +65,7 @@
     private fun resolveViews() {
         messagingLinearLayout = conversationLayout.messagingLinearLayout
         imageMessageContainer = conversationLayout.imageMessageContainer
+        messageContainers = conversationLayout.messagingGroups
         with(conversationLayout) {
             conversationIconContainer =
                     requireViewById(com.android.internal.R.id.conversation_icon_container)
@@ -146,4 +152,26 @@
         NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
         NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
     }
+
+    // Starts or stops the animations in any drawables contained in this Conversation Notification.
+    override fun setAnimationsRunning(running: Boolean) {
+        // We apply to both the child message containers in a conversation group,
+        // and the top level image message container.
+        val containers = messageContainers.asSequence().map { it.messageContainer } +
+                sequenceOf(imageMessageContainer)
+        val drawables =
+                containers
+                        .flatMap { it.children }
+                        .mapNotNull { child ->
+                            (child as? MessagingImageMessage)?.let { imageMessage ->
+                                imageMessage.drawable as? AnimatedImageDrawable
+                            }
+                        }
+        drawables.toSet().forEach {
+            when {
+                running -> it.start()
+                !running -> it.stop()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
index c587ce0..4592fde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
@@ -17,9 +17,13 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.systemui.R;
@@ -127,4 +131,40 @@
         }
         return super.getMinLayoutHeight();
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Messaging Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mMessagingLayout == null) {
+            return;
+        }
+
+        for (MessagingGroup group : mMessagingLayout.getMessagingGroups()) {
+            for (int i = 0; i < group.getMessageContainer().getChildCount(); i++) {
+                View view = group.getMessageContainer().getChildAt(i);
+                // We only need to set animations in MessagingImageMessages.
+                if (!(view instanceof MessagingImageMessage)) {
+                    continue;
+                }
+                MessagingImageMessage imageMessage =
+                        (com.android.internal.widget.MessagingImageMessage) view;
+
+                // If the drawable isn't an AnimatedImageDrawable, we can't set it to animate.
+                Drawable d = imageMessage.getDrawable();
+                if (!(d instanceof AnimatedImageDrawable)) {
+                    continue;
+                }
+                AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+                if (running) {
+                    animatedImageDrawable.start();
+                } else {
+                    animatedImageDrawable.stop();
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1c22f09..ff5b9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -403,4 +403,12 @@
         NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
         NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    public void setAnimationsRunning(boolean running) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 356ddfa..aab36da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -538,6 +538,7 @@
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
+    private boolean mHasFilteredOutSeenNotifications;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -684,6 +685,10 @@
         updateFooter();
     }
 
+    void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+        mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
+    }
+
     @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
@@ -3132,7 +3137,7 @@
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.setIconAnimationRunning(running);
+            row.setAnimationRunning(running);
         }
     }
 
@@ -4612,13 +4617,12 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void updateEmptyShadeView(
-            boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
+    void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         if (areNotificationsHiddenInShade) {
             updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
-        } else if (areSeenNotifsFiltered) {
+        } else if (mHasFilteredOutSeenNotifications) {
             updateEmptyShadeView(
                     R.string.no_unseen_notif_text,
                     R.string.unlock_to_see_notif_text,
@@ -4657,13 +4661,20 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
-        if (mFooterView == null) {
+        if (mFooterView == null || mNotificationStackSizeCalculator == null) {
             return;
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
         mFooterView.setSecondaryVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
+        if (mHasFilteredOutSeenNotifications) {
+            mFooterView.setFooterLabelTextAndIcon(
+                    R.string.unlock_to_see_notif_text,
+                    R.drawable.ic_friction_lock_closed);
+        } else {
+            mFooterView.setFooterLabelTextAndIcon(0, 0);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5891948..971dce8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1242,11 +1242,7 @@
                 // For more details, see: b/228790482
                 && !isInTransitionToKeyguard();
 
-        mView.updateEmptyShadeView(
-                shouldShow,
-                mZenModeController.areNotificationsHiddenInShade(),
-                mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
-                        && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
+        mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
 
         Trace.endSection();
     }
@@ -1942,6 +1938,9 @@
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
             mNotifStats = notifStats;
+            mView.setHasFilteredOutSeenNotifications(
+                    mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+                            && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
             updateFooter();
             updateShowEmptyShadeView();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d318759..6873de7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -26,8 +26,6 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -177,11 +175,6 @@
      */
     public void show(boolean resetSecuritySelection, boolean isScrimmed) {
         final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
-        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
-            // In split system user mode, we never unlock system user.
-            return;
-        }
-
         try {
             Trace.beginSection("KeyguardBouncer#show");
 
@@ -212,9 +205,7 @@
             }
 
             final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
-            final boolean isSystemUser =
-                UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
-            final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
+            final boolean allowDismissKeyguard = activeUserId == keyguardUserId;
 
             // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern)
             // is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
new file mode 100644
index 0000000..e092f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.telephony.ui.activity
+
+import android.app.ActivityOptions
+import android.content.DialogInterface
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.internal.app.AlertActivity
+import com.android.systemui.R
+
+/** Dialog shown to the user to switch to managed profile for making a call using work SIM. */
+class SwitchToManagedProfileForCallActivity : AlertActivity(), DialogInterface.OnClickListener {
+    private lateinit var phoneNumber: Uri
+    private var managedProfileUserId = UserHandle.USER_NULL
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+        super.onCreate(savedInstanceState)
+
+        phoneNumber = intent.getData()
+        managedProfileUserId =
+            intent.getIntExtra(
+                "android.telecom.extra.MANAGED_PROFILE_USER_ID",
+                UserHandle.USER_NULL
+            )
+
+        mAlertParams.apply {
+            mTitle = getString(R.string.call_from_work_profile_title)
+            mMessage = getString(R.string.call_from_work_profile_text)
+            mPositiveButtonText = getString(R.string.call_from_work_profile_action)
+            mNegativeButtonText = getString(R.string.call_from_work_profile_close)
+            mPositiveButtonListener = this@SwitchToManagedProfileForCallActivity
+            mNegativeButtonListener = this@SwitchToManagedProfileForCallActivity
+        }
+        setupAlert()
+    }
+
+    override fun onClick(dialog: DialogInterface?, which: Int) {
+        if (which == BUTTON_POSITIVE) {
+            switchToManagedProfile()
+        }
+        finish()
+    }
+
+    private fun switchToManagedProfile() {
+        try {
+            applicationContext.startActivityAsUser(
+                Intent(Intent.ACTION_DIAL, phoneNumber),
+                ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
+                UserHandle.of(managedProfileUserId)
+            )
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to launch activity", e)
+        }
+    }
+
+    companion object {
+        private const val TAG = "SwitchToManagedProfileForCallActivity"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5894fd3..8fff3b1 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -26,7 +26,6 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
 import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
 
-import android.annotation.Nullable;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.OnColorsChangedListener;
@@ -70,6 +69,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
+import com.android.systemui.monet.TonalPalette;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -511,39 +511,42 @@
     /**
      * Given a color candidate, return an overlay definition.
      */
-    protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) {
+    protected FabricatedOverlay getOverlay(int color, int type, Style style) {
         boolean nightMode = (mResources.getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
 
         mColorScheme = new ColorScheme(color, nightMode, style);
-        List<Integer> colorShades = type == ACCENT
-                ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
         String name = type == ACCENT ? "accent" : "neutral";
-        int paletteSize = mColorScheme.getAccent1().size();
+
         FabricatedOverlay.Builder overlay =
                 new FabricatedOverlay.Builder("com.android.systemui", name, "android");
-        for (int i = 0; i < colorShades.size(); i++) {
-            int luminosity = i % paletteSize;
-            int paletteIndex = i / paletteSize + 1;
-            String resourceName;
-            switch (luminosity) {
-                case 0:
-                    resourceName = "android:color/system_" + name + paletteIndex + "_10";
-                    break;
-                case 1:
-                    resourceName = "android:color/system_" + name + paletteIndex + "_50";
-                    break;
-                default:
-                    int l = luminosity - 1;
-                    resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
-            }
-            overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
-                    ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+
+        if (type == ACCENT) {
+            assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
+            assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
+            assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
+        } else {
+            assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
+            assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
         }
 
         return overlay.build();
     }
 
+    private void assignTonalPaletteToOverlay(String name,
+            FabricatedOverlay.Builder overlay, TonalPalette tonalPalette) {
+
+        String resourcePrefix = "android:color/system_" + name;
+        int colorDataType = TypedValue.TYPE_INT_COLOR_ARGB8;
+
+        tonalPalette.getAllShadesMapped().forEach((key, value) -> {
+            String resourceName = resourcePrefix + "_" + key;
+            int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
+            overlay.setResourceValue(resourceName, colorDataType,
+                    colorValue);
+        });
+    }
+
     /**
      * Checks if the color scheme in mColorScheme matches the current system palettes.
      * @param managedProfiles List of managed profiles for this user.
@@ -555,15 +558,15 @@
             Resources res = userHandle.isSystem()
                     ? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
             if (!(res.getColor(android.R.color.system_accent1_500, mContext.getTheme())
-                    == mColorScheme.getAccent1().get(6)
+                    == mColorScheme.getAccent1().getS500()
                     && res.getColor(android.R.color.system_accent2_500, mContext.getTheme())
-                    == mColorScheme.getAccent2().get(6)
+                    == mColorScheme.getAccent2().getS500()
                     && res.getColor(android.R.color.system_accent3_500, mContext.getTheme())
-                    == mColorScheme.getAccent3().get(6)
+                    == mColorScheme.getAccent3().getS500()
                     && res.getColor(android.R.color.system_neutral1_500, mContext.getTheme())
-                    == mColorScheme.getNeutral1().get(6)
+                    == mColorScheme.getNeutral1().getS500()
                     && res.getColor(android.R.color.system_neutral2_500, mContext.getTheme())
-                    == mColorScheme.getNeutral2().get(6))) {
+                    == mColorScheme.getNeutral2().getS500())) {
                 return false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
new file mode 100644
index 0000000..7b8235a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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.wallet.controller
+
+import android.Manifest
+import android.content.Context
+import android.content.IntentFilter
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class WalletContextualSuggestionsController
+@Inject
+constructor(
+    @Application private val applicationCoroutineScope: CoroutineScope,
+    private val walletController: QuickAccessWalletController,
+    broadcastDispatcher: BroadcastDispatcher,
+    featureFlags: FeatureFlags
+) {
+    private val allWalletCards: Flow<List<WalletCard>> =
+        if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+            conflatedCallbackFlow {
+                val callback =
+                    object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+                        override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
+                            trySendWithFailureLogging(response.walletCards, TAG)
+                        }
+
+                        override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
+                            trySendWithFailureLogging(emptyList<WalletCard>(), TAG)
+                        }
+                    }
+
+                walletController.setupWalletChangeObservers(
+                    callback,
+                    QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                )
+                walletController.updateWalletPreference()
+                walletController.queryWalletCards(callback)
+
+                awaitClose {
+                    walletController.unregisterWalletChangeObservers(
+                        QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                        QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                    )
+                }
+            }
+        } else {
+            emptyFlow()
+        }
+
+    private val contextualSuggestionsCardIds: Flow<Set<String>> =
+        if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+            broadcastDispatcher.broadcastFlow(
+                filter = IntentFilter(ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS),
+                permission = Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE,
+                flags = Context.RECEIVER_EXPORTED
+            ) { intent, _ ->
+                if (intent.hasExtra(UPDATE_CARD_IDS_EXTRA)) {
+                    intent.getStringArrayListExtra(UPDATE_CARD_IDS_EXTRA).toSet()
+                } else {
+                    emptySet()
+                }
+            }
+        } else {
+            emptyFlow()
+        }
+
+    val contextualSuggestionCards: Flow<List<WalletCard>> =
+        combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids ->
+                cards.filter { card -> ids.contains(card.cardId) }
+            }
+            .shareIn(applicationCoroutineScope, replay = 1, started = SharingStarted.Eagerly)
+
+    companion object {
+        private const val ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS =
+            "com.android.systemui.wallet.UPDATE_CONTEXTUAL_SUGGESTIONS"
+
+        private const val UPDATE_CARD_IDS_EXTRA = "cardIds"
+
+        private const val TAG = "WalletSuggestions"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index c8a352d..8795ac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -558,8 +558,8 @@
         UserInfo currentUser = mockCurrentUser(FLAG_ADMIN);
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
-        doReturn(1).when(mGlobalSettings)
-                .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+        when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU,
+                0, currentUser.id)).thenReturn(1);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
@@ -572,7 +572,7 @@
 
         when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
         doReturn(1).when(mGlobalSettings)
-                .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+                .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
 
         GlobalActionsDialogLite.BugReportAction bugReportAction =
                 mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 7205f30..8da4eae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -22,15 +22,21 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@@ -62,4 +68,24 @@
             .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
     }
+
+    @Test
+    fun `getPickerScreenState - default when launchable`() = runTest {
+        setLaunchable(true)
+
+        Truth.assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+        setLaunchable(false)
+
+        Truth.assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
+    private fun setLaunchable(isLaunchable: Boolean) {
+        whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ca44fa18..8f56b95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -114,20 +114,8 @@
     }
 
     @Test
-    fun `affordance - missing icon - model is none`() = runBlockingTest {
-        setUpState(hasWalletIcon = false)
-        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-
-        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
-        job.cancel()
-    }
-
-    @Test
     fun `affordance - no selected card - model is none`() = runBlockingTest {
-        setUpState(hasWalletIcon = false)
+        setUpState(hasSelectedCard = false)
         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +153,7 @@
     @Test
     fun `getPickerScreenState - unavailable`() = runTest {
         setUpState(
-            isWalletEnabled = false,
+            isWalletServiceAvailable = false,
         )
 
         assertThat(underTest.getPickerScreenState())
@@ -173,9 +161,9 @@
     }
 
     @Test
-    fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+    fun `getPickerScreenState - disabled when the feature is not enabled`() = runTest {
         setUpState(
-            hasWalletIcon = false,
+            isWalletEnabled = false,
         )
 
         assertThat(underTest.getPickerScreenState())
@@ -194,20 +182,16 @@
 
     private fun setUpState(
         isWalletEnabled: Boolean = true,
+        isWalletServiceAvailable: Boolean = true,
         isWalletQuerySuccessful: Boolean = true,
-        hasWalletIcon: Boolean = true,
         hasSelectedCard: Boolean = true,
     ) {
         whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
 
         val walletClient: QuickAccessWalletClient = mock()
-        val icon: Drawable? =
-            if (hasWalletIcon) {
-                ICON
-            } else {
-                null
-            }
-        whenever(walletClient.tileIcon).thenReturn(icon)
+        whenever(walletClient.tileIcon).thenReturn(ICON)
+        whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+
         whenever(walletController.walletClient).thenReturn(walletClient)
 
         whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..805dcec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+
+    private lateinit var underTest: VideoCameraQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            VideoCameraQuickAffordanceConfig(
+                context = context,
+                cameraIntents = CameraIntentsWrapper(context),
+                activityIntentHelper = activityIntentHelper,
+                userTracker = FakeUserTracker(),
+            )
+    }
+
+    @Test
+    fun `lockScreenState - visible when launchable`() = runTest {
+        setLaunchable(true)
+
+        val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+        assertThat(lockScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+    }
+
+    @Test
+    fun `lockScreenState - hidden when not launchable`() = runTest {
+        setLaunchable(false)
+
+        val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+        assertThat(lockScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+    }
+
+    @Test
+    fun `getPickerScreenState - default when launchable`() = runTest {
+        setLaunchable(true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+        setLaunchable(false)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
+    private fun setLaunchable(isLaunchable: Boolean) {
+        whenever(
+                activityIntentHelper.getTargetActivityInfo(
+                    any(),
+                    anyInt(),
+                    anyBoolean(),
+                )
+            )
+            .thenReturn(
+                if (isLaunchable) {
+                    mock()
+                } else {
+                    null
+                }
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
new file mode 100644
index 0000000..4b06905
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class TrustRepositoryTest : SysuiTestCase() {
+    @Mock private lateinit var trustManager: TrustManager
+    @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var testScope: TestScope
+    private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
+
+    private lateinit var underTest: TrustRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(users)
+
+        val logger =
+            TrustRepositoryLogger(
+                LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
+            )
+        underTest =
+            TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
+    }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+
+            val currentUserId = users[0].id
+            userRepository.setSelectedUserInfo(users[0])
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_isFalse_byDefault() =
+        testScope.runTest {
+            runCurrent()
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            userRepository.setSelectedUserInfo(users[0])
+            val listener = listenerCaptor.value
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            // current user is trusted.
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            // some other user is not trusted.
+            listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            userRepository.setSelectedUserInfo(users[1])
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+
+            userRepository.setSelectedUserInfo(users[0])
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a1b6d47..5a7a3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
@@ -56,6 +57,7 @@
  */
 @SmallTest
 @RunWith(JUnit4::class)
+@FlakyTest(bugId = 265303901)
 class KeyguardTransitionScenariosTest : SysuiTestCase() {
     private lateinit var testScope: TestScope
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 1bc4719..1a35502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -31,10 +31,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -69,7 +66,7 @@
         // Expressive applies hue rotations to the theme color. The input theme color has hue
         // 117, ensuring the hue changed significantly is a strong signal styles are being applied.
         ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
-        Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 0.1);
+        Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().getS500()).getHue(), 0.1);
     }
 
 
@@ -111,7 +108,8 @@
     public void testTertiaryHueWrapsProperly() {
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
-        int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2);
+        int tertiaryMid = colorScheme.getAccent3().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(tertiaryMid);
         Assert.assertEquals(cam.getHue(), 50.0, 10.0);
     }
@@ -121,7 +119,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.SPRITZ /* style */);
-        int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
+        int primaryMid = colorScheme.getAccent1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(primaryMid);
         Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
     }
@@ -131,7 +130,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.VIBRANT /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(neutralMid);
         Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
     }
@@ -141,7 +141,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.EXPRESSIVE /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(neutralMid);
         Assert.assertTrue(cam.getChroma() <= 8.0);
     }
@@ -151,10 +152,11 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.MONOCHROMATIC /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Assert.assertTrue(
                 Color.red(neutralMid) == Color.green(neutralMid)
-                && Color.green(neutralMid) == Color.blue(neutralMid)
+                        && Color.green(neutralMid) == Color.blue(neutralMid)
         );
     }
 
@@ -190,15 +192,14 @@
                 xml.append("        <").append(styleName).append(">");
 
                 List<String> colors = new ArrayList<>();
-                for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(),
-                        colorScheme.getAccent2().stream(),
-                        colorScheme.getAccent3().stream(),
-                        colorScheme.getNeutral1().stream(),
-                        colorScheme.getNeutral2().stream())) {
+
+                colorScheme.getAllHues().forEach(schemeHue -> {
                     colors.add("ffffff");
-                    colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect(
-                            Collectors.toList()));
-                }
+                    schemeHue.getAllShades().forEach(tone -> {
+                        colors.add(Integer.toHexString(tone).substring(2));
+                    });
+                });
+
                 xml.append(String.join(",", colors));
                 xml.append("</").append(styleName).append(">\n");
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 46a502a..ed3f1a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,15 +22,12 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
-import android.os.Bundle
 import android.os.UserHandle
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
@@ -49,7 +46,6 @@
     private val bounds = Rect(25, 25, 75, 75)
 
     private val scope = CoroutineScope(Dispatchers.Unconfined)
-    private val dispatcher = Dispatchers.Unconfined
     private val policy = FakeScreenshotPolicy()
     private val flags = FakeFeatureFlags()
 
@@ -58,7 +54,8 @@
     fun testProcessAsync() {
         flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         var result: ScreenshotRequest? = null
@@ -80,7 +77,8 @@
     fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
         flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
@@ -97,9 +95,11 @@
         policy.setManagedProfile(USER_ID, false)
         policy.setDisplayContentInfo(
             policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+        )
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
@@ -120,17 +120,20 @@
 
         // Indicate that the primary content belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
-        policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+        policy.setDisplayContentInfo(
+            policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+        )
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
 
         // Expect a task snapshot is taken, overriding the full screen mode
         assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
-        assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+        assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
         assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
         assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
         assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
@@ -147,10 +150,16 @@
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
@@ -168,10 +177,16 @@
         policy.setManagedProfile(USER_ID, false)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
@@ -190,10 +205,16 @@
         policy.setManagedProfile(USER_ID, true)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
@@ -202,14 +223,18 @@
     }
 
     private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-        val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
-            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+        val buffer =
+            HardwareBuffer.create(
+                width,
+                height,
+                HardwareBuffer.RGBA_8888,
+                1,
+                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+            )
         return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
     }
 
-    private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
-        val provided = bundleToHardwareBitmap(bundle)
-        return provided.hardwareBuffer == this.hardwareBuffer &&
-                provided.colorSpace == this.colorSpace
+    private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+        return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 99c79b0..f935019 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -35,8 +35,7 @@
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.ScreenshotHelper
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
@@ -80,24 +79,39 @@
     private val flags = FakeFeatureFlags()
     private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
 
-    private val service = TakeScreenshotService(
-        controller, userManager, devicePolicyManager, eventLogger,
-        notificationsController, mContext, Runnable::run, flags, requestProcessor)
+    private val service =
+        TakeScreenshotService(
+            controller,
+            userManager,
+            devicePolicyManager,
+            eventLogger,
+            notificationsController,
+            mContext,
+            Runnable::run,
+            flags,
+            requestProcessor
+        )
 
     @Before
     fun setUp() {
         whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
-        whenever(devicePolicyManager.getScreenCaptureDisabled(
-            /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+        whenever(
+                devicePolicyManager.getScreenCaptureDisabled(
+                    /* admin component (null: any admin) */ isNull(),
+                    eq(UserHandle.USER_ALL)
+                )
+            )
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
-            val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
-            val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
-            consumer.accept(request)
-        }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
+                val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+                val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+                consumer.accept(request)
+            }
+            .`when`(requestProcessor)
+            .processAsync(/* request= */ any(), /* callback= */ any())
 
         // Flipped in selected test cases
         flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -108,7 +122,8 @@
             /* className = */ null,
             /* token = */ null,
             application,
-            /* activityManager = */ null)
+            /* activityManager = */ null
+        )
     }
 
     @Test
@@ -125,63 +140,89 @@
 
     @Test
     fun takeScreenshotFullscreen() {
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+                .setTopComponent(topComponent)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
-        verify(controller, times(1)).takeScreenshotFullscreen(
-            eq(topComponent),
-            /* onSavedListener = */ any(),
-            /* requestCallback = */ any())
+        verify(controller, times(1))
+            .takeScreenshotFullscreen(
+                eq(topComponent),
+                /* onSavedListener = */ any(),
+                /* requestCallback = */ any()
+            )
 
         assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
         val logEvent = eventLogger.get(0)
 
-        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
-            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
-        assertEquals("Expected supplied package name",
-            topComponent.packageName, eventLogger.get(0).packageName)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId,
+            SCREENSHOT_REQUESTED_KEY_CHORD.id
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            eventLogger.get(0).packageName
+        )
     }
 
     @Test
     fun takeScreenshotProvidedImage() {
         val bounds = Rect(50, 50, 150, 150)
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
+                .setTopComponent(topComponent)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
-        verify(controller, times(1)).handleImageAsScreenshot(
-            argThat { b -> b.equalsHardwareBitmap(bitmap) },
-            eq(bounds),
-            eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
-            /* onSavedListener = */ any(), /* requestCallback = */ any())
+        verify(controller, times(1))
+            .handleImageAsScreenshot(
+                argThat { b -> b.equalsHardwareBitmap(bitmap) },
+                eq(bounds),
+                eq(Insets.NONE),
+                eq(TASK_ID),
+                eq(USER_ID),
+                eq(topComponent),
+                /* onSavedListener = */ any(),
+                /* requestCallback = */ any()
+            )
 
         assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
         val logEvent = eventLogger.get(0)
 
-        assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
-            logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
-        assertEquals("Expected supplied package name",
-            topComponent.packageName, eventLogger.get(0).packageName)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            logEvent.eventId,
+            SCREENSHOT_REQUESTED_OVERVIEW.id
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            eventLogger.get(0).packageName
+        )
     }
 
     @Test
     fun takeScreenshotFullscreen_userLocked() {
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+                .setTopComponent(topComponent)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
         verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
         verify(callback, times(1)).reportError()
@@ -190,21 +231,24 @@
 
     @Test
     fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
-        whenever(devicePolicyManager.getScreenCaptureDisabled(
-            isNull(), eq(UserHandle.USER_ALL))
-        ).thenReturn(true)
+        whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+            .thenReturn(true)
 
-        whenever(devicePolicyResourcesManager.getString(
-            eq(SCREENSHOT_BLOCKED_BY_ADMIN),
-            /* Supplier<String> */ any(),
-        )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+        whenever(
+                devicePolicyResourcesManager.getString(
+                    eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+                    /* Supplier<String> */
+                    any(),
+                )
+            )
+            .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
 
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+                .setTopComponent(topComponent)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
         // error shown: Toast.makeText(...).show(), untestable
         verify(callback, times(1)).reportError()
@@ -214,14 +258,20 @@
 
 private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
     return config == HARDWARE &&
-            other.config == HARDWARE &&
-            hardwareBuffer == other.hardwareBuffer &&
-            colorSpace == other.colorSpace
+        other.config == HARDWARE &&
+        hardwareBuffer == other.hardwareBuffer &&
+        colorSpace == other.colorSpace
 }
 
 /** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
 private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-    val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
-        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+    val buffer =
+        HardwareBuffer.create(
+            width,
+            height,
+            HardwareBuffer.RGBA_8888,
+            1,
+            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+        )
     return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index f802a5e..ed9baf5b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -109,11 +109,12 @@
     @Test
     fun testEdgeElementsAlignedWithEdge_largeScreen() {
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+            assertThat(getConstraint(R.id.clock).layout.startToEnd).isEqualTo(R.id.begin_guide)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
 
-            assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+            assertThat(getConstraint(R.id.privacy_container).layout.endToStart)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(0.5f)
         }
     }
 
@@ -219,7 +220,12 @@
                 .isEqualTo(cutoutEnd - padding)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+                .isEqualTo(cutoutStart - padding)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+                .isEqualTo(cutoutEnd - padding)
+        }
     }
 
     @Test
@@ -246,7 +252,10 @@
             assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 9d531a1..4559a23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -44,16 +44,23 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.graphics.Color;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -61,6 +68,7 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
 import org.junit.Assert;
@@ -72,6 +80,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
 import java.util.List;
 
 @SmallTest
@@ -96,6 +105,9 @@
                 mDependency,
                 TestableLooper.get(this));
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
+        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+        mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
         // create a standard private notification row
         Notification normalNotif = mNotificationTestHelper.createNotification();
         normalNotif.publicVersion = null;
@@ -559,4 +571,123 @@
         Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
         Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
     }
+
+    @Test
+    public void testSetContentAnimationRunning_Run() throws Exception {
+        // Create views for the notification row.
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateLayout);
+
+        mNotifRow.setAnimationRunning(true);
+        verify(publicLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+    @Test
+    public void testSetContentAnimationRunning_Stop() {
+        // Create views for the notification row.
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateLayout);
+
+        mNotifRow.setAnimationRunning(false);
+        verify(publicLayout, times(1)).setContentAnimationRunning(false);
+        verify(privateLayout, times(1)).setContentAnimationRunning(false);
+    }
+
+    @Test
+    public void testSetContentAnimationRunningInGroupChild_Run() {
+        // Creates parent views on mGroupRow.
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPrivateLayout(privateParentLayout);
+
+        // Create child views on mNotifRow.
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateChildLayout);
+        when(mNotifRow.isGroupExpanded()).thenReturn(true);
+        setMockChildrenContainer(mGroupRow, mNotifRow);
+
+        mGroupRow.setAnimationRunning(true);
+        verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
+        // The child layouts should be started too.
+        verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+
+    @Test
+    public void testSetIconAnimationRunningGroup_Run() {
+        // Create views for a group row.
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        mGroupRow.setPrivateLayout(privateParentLayout);
+        when(mGroupRow.isGroupExpanded()).thenReturn(true);
+
+        // Sets up mNotifRow as a child ExpandableNotificationRow.
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        mNotifRow.setPrivateLayout(privateChildLayout);
+        when(mNotifRow.isGroupExpanded()).thenReturn(true);
+
+        NotificationChildrenContainer mockContainer =
+                setMockChildrenContainer(mGroupRow, mNotifRow);
+
+        // Mock the children view wrappers, and give them each an icon.
+        NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
+        CachingIconView mockIcon = mock(CachingIconView.class);
+        when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
+
+        NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper);
+        CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
+        when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
+
+        // Give the icon image views drawables, so we can make sure they animate.
+        // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
+        AnimationDrawable drawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
+
+        AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
+
+        mGroupRow.setAnimationRunning(true);
+        verify(drawable, times(1)).start();
+        verify(vectorDrawable, times(1)).start();
+        verify(lowPriDrawable, times(1)).start();
+        verify(lowPriVectorDrawable, times(1)).start();
+    }
+
+    private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
+            Drawable rightIconDrawable) {
+        ImageView iconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
+        when(iconView.getDrawable()).thenReturn(iconDrawable);
+
+        ImageView rightIconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
+        when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
+    }
+
+    private NotificationChildrenContainer setMockChildrenContainer(
+            ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
+        List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
+        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
+        when(mockContainer.getNotificationChildCount()).thenReturn(1);
+        when(mockContainer.getAttachedChildren()).thenReturn(rowList);
+        parentRow.setChildrenContainer(mockContainer);
+        return mockContainer;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
index 1f92b0a..819a75b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
@@ -98,5 +100,16 @@
 
         mView.setSecondaryVisible(true /* visible */, true /* animate */);
     }
+
+    @Test
+    public void testSetFooterLabelTextAndIcon() {
+        mView.setFooterLabelTextAndIcon(
+                R.string.unlock_to_see_notif_text,
+                R.drawable.ic_friction_lock_closed);
+        assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
+        assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
+        assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
+                .isEqualTo(View.VISIBLE);
+    }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 562b4df..7b2051d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -34,9 +34,12 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,6 +47,7 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations.initMocks
 
@@ -305,6 +309,86 @@
         assertEquals(0, getMarginBottom(actionListMarginTarget))
     }
 
+    @Test
+    fun onSetAnimationRunning() {
+        // Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+
+        // When: we set content animation running true _again_.
+        assertFalse(view.setContentAnimationRunning(true))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        // Verify counts number of calls so far on the object, so these still register as 1.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+    }
+
+    @Test
+    fun onSetAnimationStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted).setAnimationsRunning(true)
+        verify(mockExpanded).setAnimationsRunning(true)
+        verify(mockHeadsUp).setAnimationsRunning(true)
+
+        // When: we set content animation running false, the state changes, so the function
+        // returns true.
+        assertTrue(view.setContentAnimationRunning(false))
+
+        // Then: the children have their animations stopped.
+        verify(mockContracted).setAnimationsRunning(false)
+        verify(mockExpanded).setAnimationsRunning(false)
+        verify(mockHeadsUp).setAnimationsRunning(false)
+    }
+
+    @Test
+    fun onSetAnimationInitStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we try to stop the animations before they've been started.
+        assertFalse(view.setContentAnimationRunning(false))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        verify(mockContracted, never()).setAnimationsRunning(false)
+        verify(mockExpanded, never()).setAnimationsRunning(false)
+        verify(mockHeadsUp, never()).setAnimationsRunning(false)
+    }
+
     private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
         mock<ExpandableNotificationRow>().apply {
             whenever(this.entry).thenReturn(notificationEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 59d4720..e6f6a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -130,6 +131,7 @@
     public final OnUserInteractionCallback mOnUserInteractionCallback;
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
+    private FeatureFlags mFeatureFlags;
 
     public NotificationTestHelper(
             Context context,
@@ -191,12 +193,17 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
+        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
+    public void setFeatureFlags(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
+    }
+
     public ExpandableNotificationRowLogger getMockLogger() {
         return mMockLogger;
     }
@@ -559,7 +566,8 @@
                 mock(NotificationGutsManager.class),
                 mock(MetricsLogger.class),
                 mock(SmartReplyConstants.class),
-                mock(SmartReplyController.class));
+                mock(SmartReplyController.class),
+                mFeatureFlags);
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index 509ba41..8f88501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
@@ -16,11 +16,12 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
-import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.testing.AndroidTestingRunner;
@@ -28,11 +29,11 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -73,4 +74,38 @@
                 Notification.EXTRA_LARGE_ICON_BIG, new Bundle());
         wrapper.onContentUpdated(mRow);
     }
+
+    @Test
+    public void setAnimationsRunning_Run() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(true);
+        verify(mockDrawable).start();
+    }
+
+    @Test
+    public void setAnimationsRunning_Stop() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(false);
+        verify(mockDrawable).stop();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..3fa68bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+        verify(mockDrawable2).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+        verify(mockDrawable2).stop()
+    }
+
+    private fun fakeConversationLayout(
+        mockDrawableGroupMessage: AnimatedImageDrawable,
+        mockDrawableImageMessage: AnimatedImageDrawable
+    ): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableImageMessage)
+            }
+        val mockImageMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+
+        val mockMessagingImageMessageForGroup: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableGroupMessage)
+            }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessageForGroup)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<ConversationLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+                whenever(imageMessageContainer).thenReturn(mockImageMessageContainer)
+                whenever(messagingLinearLayout).thenReturn(mockMessageContainer)
+
+                // These must be mocked as they're required to be nonnull.
+                whenever(requireViewById<View>(R.id.conversation_icon_container)).thenReturn(mock())
+                whenever(requireViewById<CachingIconView>(R.id.conversation_icon))
+                    .thenReturn(mock())
+                whenever(findViewById<CachingIconView>(R.id.icon)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_bg)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button_container)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_ring))
+                    .thenReturn(mock())
+                whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+            }
+        return mockView
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..c0444b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLayout
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+    }
+
+    private fun fakeMessagingLayout(mockDrawable: AnimatedImageDrawable): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply { whenever(drawable).thenReturn(mockDrawable) }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<MessagingLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+            }
+        return mockView
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 645052f..3d3b91d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -65,6 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -139,6 +141,9 @@
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
+    private final SeenNotificationsProviderImpl mSeenNotificationsProvider =
+            new SeenNotificationsProviderImpl();
+
     private NotificationStackScrollLayoutController mController;
 
     @Before
@@ -180,7 +185,7 @@
                 mUiEventLogger,
                 mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
-                new SeenNotificationsProviderImpl(),
+                mSeenNotificationsProvider,
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
@@ -233,16 +238,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ true,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ true);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ true,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ true);
     }
 
     @Test
@@ -255,16 +258,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
     }
 
     @Test
@@ -283,16 +284,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
 
         mController.setQsFullScreen(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
     }
 
     @Test
@@ -400,6 +399,17 @@
         verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
     }
 
+    @Test
+    public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
+        when(mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()).thenReturn(true);
+        mSeenNotificationsProvider.setHasFilteredOutSeenNotifications(true);
+        mController.attach(mNotificationStackScrollLayout);
+        mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+        verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
+        verify(mNotificationStackScrollLayout).updateFooter();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7622549..dd7143a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -30,6 +30,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertFalse;
+import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +54,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
@@ -328,7 +330,7 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true, false);
+        mStackScroller.updateEmptyShadeView(true, true);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -338,7 +340,7 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false, false);
+        mStackScroller.updateEmptyShadeView(true, false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -347,10 +349,10 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false, false);
+        mStackScroller.updateEmptyShadeView(true, false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true, false);
+        mStackScroller.updateEmptyShadeView(true, true);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -818,6 +820,29 @@
         assertEquals(0f, mAmbientState.getStackY());
     }
 
+    @Test
+    public void hasFilteredOutSeenNotifs_updateFooter() {
+        mStackScroller.setCurrentUserSetup(true);
+
+        // add footer
+        mStackScroller.inflateFooterView();
+        TextView footerLabel =
+                mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
+
+        mStackScroller.setHasFilteredOutSeenNotifications(true);
+        mStackScroller.updateFooter();
+
+        assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
+        mStackScroller.setHasFilteredOutSeenNotifications(true);
+        mStackScroller.updateEmptyShadeView(true, false);
+
+        verify(mEmptyShadeView).setFooterText(not(0));
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 2a93fff..d53e09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -790,15 +790,15 @@
 
         reset(mResources);
         when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().getS500());
         when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().getS500());
         when(mResources.getColor(eq(android.R.color.system_accent3_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().getS500());
         when(mResources.getColor(eq(android.R.color.system_neutral1_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().getS500());
         when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().getS500());
 
         // Defers event because we already have initial colors.
         verify(mThemeOverlayApplier, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
new file mode 100644
index 0000000..b527861
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 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.wallet.controller
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.ArrayList
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class WalletContextualSuggestionsControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: QuickAccessWalletController
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var mockContext: Context
+    @Captor private lateinit var broadcastReceiver: ArgumentCaptor<BroadcastReceiver>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(
+                broadcastDispatcher.broadcastFlow<List<String>?>(
+                    any(),
+                    isNull(),
+                    any(),
+                    any(),
+                    any()
+                )
+            )
+            .thenCallRealMethod()
+
+        whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+            .thenReturn(true)
+
+        whenever(CARD_1.cardId).thenReturn(ID_1)
+        whenever(CARD_2.cardId).thenReturn(ID_2)
+        whenever(CARD_3.cardId).thenReturn(ID_3)
+    }
+
+    @Test
+    fun `state - has wallet cards - received contextual cards`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).containsExactly(CARD_1, CARD_2)
+    }
+
+    @Test
+    fun `state - no wallet cards - received contextual cards`() = runTest {
+        setUpWalletClient(emptyList())
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - has wallet cards - no contextual cards`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(mockContext, createContextualCardsIntent(emptyList()))
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - wallet cards error`() = runTest {
+        setUpWalletClient(shouldFail = true)
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - no contextual cards extra`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(mockContext, Intent(INTENT_NAME))
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - has wallet cards - received contextual cards - feature disabled`() = runTest {
+        whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+            .thenReturn(false)
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verify(broadcastDispatcher, never()).broadcastFlow(any(), isNull(), any(), any())
+        assertThat(latest()).isNull()
+    }
+
+    private fun createWalletContextualSuggestionsController(
+        scope: CoroutineScope
+    ): WalletContextualSuggestionsController {
+        return WalletContextualSuggestionsController(
+            scope,
+            walletController,
+            broadcastDispatcher,
+            featureFlags
+        )
+    }
+
+    private fun verifyRegistered() {
+        verify(broadcastDispatcher)
+            .registerReceiver(capture(broadcastReceiver), any(), isNull(), isNull(), any(), any())
+    }
+
+    private fun createContextualCardsIntent(
+        ids: List<String> = emptyList(),
+    ): Intent {
+        val intent = Intent(INTENT_NAME)
+        intent.putStringArrayListExtra("cardIds", ArrayList(ids))
+        return intent
+    }
+
+    private fun setUpWalletClient(
+        cards: List<WalletCard> = emptyList(),
+        shouldFail: Boolean = false
+    ) {
+        whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+            with(
+                invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+            ) {
+                if (shouldFail) {
+                    onWalletCardRetrievalError(mock())
+                } else {
+                    onWalletCardsRetrieved(GetWalletCardsResponse(cards, 0))
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val ID_1: String = "123"
+        private val CARD_1: WalletCard = mock()
+        private const val ID_2: String = "456"
+        private val CARD_2: WalletCard = mock()
+        private const val ID_3: String = "789"
+        private val CARD_3: WalletCard = mock()
+        private val INTENT_NAME: String = "WalletSuggestionsIntent"
+    }
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 5ee30fb7..e549b61 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -31,7 +31,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.graphics.Rect;
-import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -43,8 +42,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.File;
@@ -52,39 +49,60 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+/**
+ * Backs up and restores wallpaper and metadata related to it.
+ *
+ * This agent has its own package because it does full backup as opposed to SystemBackupAgent
+ * which does key/value backup.
+ *
+ * This class stages wallpaper files for backup by copying them into its own directory because of
+ * the following reasons:
+ *
+ * <ul>
+ *     <li>Non-system users don't have permission to read the directory that the system stores
+ *     the wallpaper files in</li>
+ *     <li>{@link BackupAgent} enforces that backed up files must live inside the package's
+ *     {@link Context#getFilesDir()}</li>
+ * </ul>
+ *
+ * There are 3 files to back up:
+ * <ul>
+ *     <li>The "wallpaper info"  file which contains metadata like the crop applied to the
+ *     wallpaper or the live wallpaper component name.</li>
+ *     <li>The "system" wallpaper file.</li>
+ *     <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system
+ *     wallpaper if set.</li>
+ * </ul>
+ *
+ * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the
+ * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be
+ * part of the metadata file and the wallpaper will be applied when the package it's installed.
+ */
 public class WallpaperBackupAgent extends BackupAgent {
     private static final String TAG = "WallpaperBackup";
     private static final boolean DEBUG = false;
 
-    // NB: must be kept in sync with WallpaperManagerService but has no
-    // compile-time visibility.
+    // Names of our local-data stage files
+    @VisibleForTesting
+    static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage";
+    @VisibleForTesting
+    static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
+    @VisibleForTesting
+    static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
 
-    // Target filenames within the system's wallpaper directory
-    static final String WALLPAPER = "wallpaper_orig";
-    static final String WALLPAPER_LOCK = "wallpaper_lock_orig";
-    static final String WALLPAPER_INFO = "wallpaper_info.xml";
-
-    // Names of our local-data stage files/links
-    static final String IMAGE_STAGE = "wallpaper-stage";
-    static final String LOCK_IMAGE_STAGE = "wallpaper-lock-stage";
-    static final String INFO_STAGE = "wallpaper-info-stage";
     static final String EMPTY_SENTINEL = "empty";
     static final String QUOTA_SENTINEL = "quota";
 
-    // Not-for-backup bookkeeping
+    // Shared preferences constants.
     static final String PREFS_NAME = "wbprefs.xml";
     static final String SYSTEM_GENERATION = "system_gen";
     static final String LOCK_GENERATION = "lock_gen";
 
-    private File mWallpaperInfo;        // wallpaper metadata file
-    private File mWallpaperFile;        // primary wallpaper image file
-    private File mLockWallpaperFile;    // lock wallpaper image file
-
     // If this file exists, it means we exceeded our quota last time
     private File mQuotaFile;
     private boolean mQuotaExceeded;
 
-    private WallpaperManager mWm;
+    private WallpaperManager mWallpaperManager;
 
     @Override
     public void onCreate() {
@@ -92,11 +110,7 @@
             Slog.v(TAG, "onCreate()");
         }
 
-        File wallpaperDir = getWallpaperDir();
-        mWallpaperInfo = new File(wallpaperDir, WALLPAPER_INFO);
-        mWallpaperFile = new File(wallpaperDir, WALLPAPER);
-        mLockWallpaperFile = new File(wallpaperDir, WALLPAPER_LOCK);
-        mWm = (WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE);
+        mWallpaperManager = getSystemService(WallpaperManager.class);
 
         mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL);
         mQuotaExceeded = mQuotaFile.exists();
@@ -105,111 +119,39 @@
         }
     }
 
-    @VisibleForTesting
-    protected File getWallpaperDir() {
-        return Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM);
-    }
-
     @Override
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
-        // To avoid data duplication and disk churn, use links as the stage.
-        final File filesDir = getFilesDir();
-        final File infoStage = new File(filesDir, INFO_STAGE);
-        final File imageStage = new File (filesDir, IMAGE_STAGE);
-        final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
-        final File empty = new File (filesDir, EMPTY_SENTINEL);
-
         try {
             // We always back up this 'empty' file to ensure that the absence of
             // storable wallpaper imagery still produces a non-empty backup data
             // stream, otherwise it'd simply be ignored in preflight.
+            final File empty = new File(getFilesDir(), EMPTY_SENTINEL);
             if (!empty.exists()) {
                 FileOutputStream touch = new FileOutputStream(empty);
                 touch.close();
             }
             backupFile(empty, data);
 
-            SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
-            final int lastSysGeneration = prefs.getInt(SYSTEM_GENERATION, -1);
-            final int lastLockGeneration = prefs.getInt(LOCK_GENERATION, -1);
+            SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
 
-            final int sysGeneration =
-                    mWm.getWallpaperIdForUser(FLAG_SYSTEM, UserHandle.USER_SYSTEM);
-            final int lockGeneration =
-                    mWm.getWallpaperIdForUser(FLAG_LOCK, UserHandle.USER_SYSTEM);
+            // Check the IDs of the wallpapers that we backed up last time. If they haven't
+            // changed, we won't re-stage them for backup and use the old staged versions to avoid
+            // disk churn.
+            final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
+            final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
+            final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
+            final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
             final boolean sysChanged = (sysGeneration != lastSysGeneration);
             final boolean lockChanged = (lockGeneration != lastLockGeneration);
 
-            final boolean sysEligible = mWm.isWallpaperBackupEligible(FLAG_SYSTEM);
-            final boolean lockEligible = mWm.isWallpaperBackupEligible(FLAG_LOCK);
-
-                // There might be a latent lock wallpaper file present but unused: don't
-                // include it in the backup if that's the case.
-                ParcelFileDescriptor lockFd = mWm.getWallpaperFile(FLAG_LOCK, UserHandle.USER_SYSTEM);
-                final boolean hasLockWallpaper = (lockFd != null);
-                IoUtils.closeQuietly(lockFd);
-
             if (DEBUG) {
                 Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged);
                 Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
-                Slog.v(TAG, "sysEligble=" + sysEligible);
-                Slog.v(TAG, "lockEligible=" + lockEligible);
-                Slog.v(TAG, "hasLockWallpaper=" + hasLockWallpaper);
             }
 
-            // only back up the wallpapers if we've been told they're eligible
-            if (mWallpaperInfo.exists()) {
-                if (sysChanged || lockChanged || !infoStage.exists()) {
-                    if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
-                    FileUtils.copyFileOrThrow(mWallpaperInfo, infoStage);
-                }
-                if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
-                backupFile(infoStage, data);
-            } else {
-                Slog.w(TAG, "Wallpaper metadata file doesn't exist: " +
-                        mWallpaperFile.getPath());
-            }
-            if (sysEligible && mWallpaperFile.exists()) {
-                if (sysChanged || !imageStage.exists()) {
-                    if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
-                    FileUtils.copyFileOrThrow(mWallpaperFile, imageStage);
-                }
-                if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
-                backupFile(imageStage, data);
-                prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
-            } else {
-                Slog.w(TAG, "Not backing up wallpaper as one of conditions is not "
-                        + "met: sysEligible = " + sysEligible + " wallpaperFileExists = "
-                        + mWallpaperFile.exists());
-            }
-
-            // If there's no lock wallpaper, then we have nothing to add to the backup.
-            if (lockGeneration == -1) {
-                if (lockChanged && lockImageStage.exists()) {
-                    if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
-                    lockImageStage.delete();
-                }
-                Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
-                prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
-                return;
-            }
-
-            // Don't try to store the lock image if we overran our quota last time
-            if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) {
-                if (lockChanged || !lockImageStage.exists()) {
-                    if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
-                    FileUtils.copyFileOrThrow(mLockWallpaperFile, lockImageStage);
-                }
-                if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
-                backupFile(lockImageStage, data);
-                prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
-            } else {
-                Slog.w(TAG, "Not backing up lockscreen wallpaper as one of conditions is not "
-                        + "met: lockEligible = " + lockEligible + " hasLockWallpaper = "
-                        + hasLockWallpaper + " lockWallpaperFileExists = "
-                        + mLockWallpaperFile.exists() + " quotaExceeded (last time) = "
-                        + mQuotaExceeded);
-            }
+            backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
+            backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
+            backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to back up wallpaper", e);
         } finally {
@@ -222,6 +164,114 @@
         }
     }
 
+    private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
+            throws IOException {
+        final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
+
+        if (wallpaperInfoFd == null) {
+            Slog.w(TAG, "Wallpaper metadata file doesn't exist");
+            return;
+        }
+
+        final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE);
+
+        if (sysOrLockChanged || !infoStage.exists()) {
+            if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
+            copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage);
+        }
+
+        if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
+        backupFile(infoStage, data);
+    }
+
+    private void backupSystemWallpaperFile(SharedPreferences sharedPrefs,
+            boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException {
+        if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) {
+            Slog.d(TAG, "System wallpaper ineligible for backup");
+            return;
+        }
+
+        final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile(
+                FLAG_SYSTEM,
+                /* getCropped= */ false);
+
+        if (systemWallpaperImageFd == null) {
+            Slog.w(TAG, "System wallpaper doesn't exist");
+            return;
+        }
+
+        final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE);
+
+        if (sysChanged || !imageStage.exists()) {
+            if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
+            copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage);
+        }
+
+        if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
+        backupFile(imageStage, data);
+        sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
+    }
+
+    private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
+            boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
+        final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
+
+        // This means there's no lock wallpaper set by the user.
+        if (lockGeneration == -1) {
+            if (lockChanged && lockImageStage.exists()) {
+                if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
+                lockImageStage.delete();
+            }
+            Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
+            sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+            return;
+        }
+
+        if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) {
+            Slog.d(TAG, "Lock screen wallpaper ineligible for backup");
+            return;
+        }
+
+        final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile(
+                FLAG_LOCK, /* getCropped= */ false);
+
+        // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper
+        // set, but we can't find it.
+        if (lockWallpaperFd == null) {
+            Slog.w(TAG, "Lock wallpaper doesn't exist");
+            return;
+        }
+
+        if (mQuotaExceeded) {
+            Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time");
+            return;
+        }
+
+        if (lockChanged || !lockImageStage.exists()) {
+            if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
+            copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage);
+        }
+
+        if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
+        backupFile(lockImageStage, data);
+        sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+    }
+
+    /**
+     * Copies the contents of the given {@code pfd} to the given {@code file}.
+     *
+     * All resources used in the process including the {@code pfd} will be closed.
+     */
+    private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)
+            throws IOException {
+        try (ParcelFileDescriptor.AutoCloseInputStream inputStream =
+                     new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+             FileOutputStream outputStream = new FileOutputStream(file)
+        ) {
+            FileUtils.copy(inputStream, outputStream);
+        }
+    }
+
     @VisibleForTesting
     // fullBackupFile is final, so we intercept backups here in tests.
     protected void backupFile(File file, FullBackupDataOutput data) {
@@ -244,9 +294,9 @@
     public void onRestoreFinished() {
         Slog.v(TAG, "onRestoreFinished()");
         final File filesDir = getFilesDir();
-        final File infoStage = new File(filesDir, INFO_STAGE);
-        final File imageStage = new File (filesDir, IMAGE_STAGE);
-        final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
+        final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
+        final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
+        final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
 
         // If we restored separate lock imagery, the system wallpaper should be
         // applied as system-only; but if there's no separate lock image, make
@@ -283,11 +333,11 @@
     void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
         if (servicePackageExists(wpService)) {
             Slog.i(TAG, "Using wallpaper service " + wpService);
-            mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
+            mWallpaperManager.setWallpaperComponent(wpService);
             if (applyToLock) {
                 // We have a live wallpaper and no static lock image,
                 // allow live wallpaper to show "through" on lock screen.
-                mWm.clear(FLAG_LOCK);
+                mWallpaperManager.clear(FLAG_LOCK);
             }
         } else {
             // If we've restored a live wallpaper, but the component doesn't exist,
@@ -311,8 +361,9 @@
                 Slog.i(TAG, "Got restored wallpaper; applying which=" + which
                         + "; cropHint = " + cropHint);
                 try (FileInputStream in = new FileInputStream(stage)) {
-                    mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which);
-                } finally {} // auto-closes 'in'
+                    mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
+                            which);
+                }
             }
         } else {
             Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
@@ -384,7 +435,7 @@
             if (comp != null) {
                 final IPackageManager pm = AppGlobals.getPackageManager();
                 final PackageInfo info = pm.getPackageInfo(comp.getPackageName(),
-                        0, UserHandle.USER_SYSTEM);
+                        0, getUserId());
                 return (info != null);
             }
         } catch (RemoteException e) {
@@ -393,16 +444,14 @@
         return false;
     }
 
-    //
-    // Key/value API: abstract, therefore required; but not used
-    //
-
+    /** Unused Key/Value API. */
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) throws IOException {
         // Intentionally blank
     }
 
+    /** Unused Key/Value API. */
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
@@ -427,10 +476,10 @@
 
                 if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
-                    mWm.setWallpaperComponent(componentName);
+                    mWallpaperManager.setWallpaperComponent(componentName);
                     if (applyToLock) {
                         try {
-                            mWm.clear(FLAG_LOCK);
+                            mWallpaperManager.clear(FLAG_LOCK);
                         } catch (IOException e) {
                             Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
                         }
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 4367075..20dd5165 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -18,14 +18,15 @@
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
+import static com.android.wallpaperbackup.WallpaperBackupAgent.LOCK_WALLPAPER_STAGE;
+import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE;
+import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -35,43 +36,42 @@
 import android.app.backup.FullBackupDataOutput;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.UserHandle;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.content.PackageMonitor;
-import com.android.wallpaperbackup.WallpaperBackupAgent;
 import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 @RunWith(AndroidJUnit4.class)
 public class WallpaperBackupAgentTest {
-    private static final String SYSTEM_GENERATION = "system_gen";
-    private static final String LOCK_GENERATION = "lock_gen";
     private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package";
 
     private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
     private static final int TEST_LOCK_WALLPAPER_ID = 2;
+    private static final int NO_LOCK_WALLPAPER_ID = -1;
 
     @Mock private FullBackupDataOutput mOutput;
     @Mock private WallpaperManager mWallpaperManager;
-    @Mock private SharedPreferences mSharedPreferences;
-    @Mock private SharedPreferences.Editor mSharedPreferenceEditor;
     @Mock private Context mMockContext;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -84,14 +84,11 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mSharedPreferences.edit()).thenReturn(mSharedPreferenceEditor);
-        when(mSharedPreferenceEditor.putInt(anyString(), anyInt()))
-                .thenReturn(mSharedPreferenceEditor);
-        doNothing().when(mSharedPreferenceEditor).apply();
+        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
+        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
 
         mContext = new ContextWithServiceOverrides(ApplicationProvider.getApplicationContext());
         mContext.injectSystemService(WallpaperManager.class, mWallpaperManager);
-        mContext.setSharedPreferencesOverride(mSharedPreferences);
 
         mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
         mWallpaperBackupAgent.attach(mContext);
@@ -100,48 +97,236 @@
         mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
     }
 
-    @Test
-    public void testOnFullBackup_withNoChanges_onlyBacksUpEmptyFile() throws IOException {
-        mockBackedUpState();
-        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
-
-        mWallpaperBackupAgent.onFullBackup(mOutput);
-
-        assertThat(mWallpaperBackupAgent.mBackedUpFiles.size()).isEqualTo(1);
-        assertThat(mWallpaperBackupAgent.mBackedUpFiles.get(0).getName()).isEqualTo("empty");
+    @After
+    public void tearDown() {
+        FileUtils.deleteContents(mContext.getFilesDir());
     }
 
     @Test
-    public void testOnFullBackup_withOnlyChangedSystem_updatesTheSharedPreferences()
-            throws IOException {
-        mockSystemWallpaperReadyToBackUp();
-        mockUnbackedUpState();
-        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
-
+    public void testOnFullBackup_backsUpEmptyFile() throws IOException {
         mWallpaperBackupAgent.onFullBackup(mOutput);
 
-        verify(mSharedPreferenceEditor).putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
+        assertThat(getBackedUpFileOptional("empty").isPresent()).isTrue();
     }
 
     @Test
-    public void testOnFullBackup_withLockChangedToMatchSystem_updatesTheSharedPreferences()
-            throws IOException {
-        mockBackedUpState();
-        mockSystemWallpaperReadyToBackUp();
-        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, -1);
+    public void testOnFullBackup_noExistingInfoStage_backsUpInfoFile() throws Exception {
+        mockWallpaperInfoFileWithContents("fake info file");
 
         mWallpaperBackupAgent.onFullBackup(mOutput);
 
-        InOrder inOrder = inOrder(mSharedPreferenceEditor);
-        inOrder.verify(mSharedPreferenceEditor)
-                .putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
-        inOrder.verify(mSharedPreferenceEditor).apply();
-        inOrder.verify(mSharedPreferenceEditor).putInt(eq(LOCK_GENERATION), eq(-1));
-        inOrder.verify(mSharedPreferenceEditor).apply();
+        assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+                "fake info file");
     }
 
     @Test
-    public void updateWallpaperComponent_doesApplyLater() throws IOException {
+    public void testOnFullBackup_existingInfoStage_noChange_backsUpAlreadyStagedInfoFile()
+            throws Exception {
+        // Do a backup first so the info file is staged.
+        mockWallpaperInfoFileWithContents("old info file");
+        // Provide system and lock wallpapers but don't change them in between backups.
+        mockSystemWallpaperFileWithContents("system wallpaper");
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // This new wallpaper should be ignored since the ID of neither wallpaper changed.
+        mockWallpaperInfoFileWithContents("new info file");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+                "old info file");
+    }
+
+    @Test
+    public void testOnFullBackup_existingInfoStage_sysChanged_backsUpNewInfoFile()
+            throws Exception {
+        // Do a backup first so the backed up system wallpaper ID is persisted to disk.
+        mockWallpaperInfoFileWithContents("old info file");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock that the user changed the system wallpaper.
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID + 1, TEST_LOCK_WALLPAPER_ID);
+        mockWallpaperInfoFileWithContents("new info file");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+                "new info file");
+    }
+
+    @Test
+    public void testOnFullBackup_existingInfoStage_lockChanged_backsUpNewInfoFile()
+            throws Exception {
+        // Do a backup first so the backed up lock wallpaper ID is persisted to disk.
+        mockWallpaperInfoFileWithContents("old info file");
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock that the user changed the system wallpaper.
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID + 1);
+        mockWallpaperInfoFileWithContents("new info file");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+                "new info file");
+    }
+
+    @Test
+    public void testOnFullBackup_systemWallpaperNotEligible_doesNotBackUpSystemWallpaper()
+            throws Exception {
+        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(false);
+        mockSystemWallpaperFileWithContents("system wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertThat(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).isPresent()).isFalse();
+    }
+
+    @Test
+    public void testOnFullBackup_existingSystemStage_noSysChange_backsUpAlreadyStagedFile()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockSystemWallpaperFileWithContents("system wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // This new file should be ignored since the ID of the wallpaper did not change.
+        mockSystemWallpaperFileWithContents("new system wallpaper");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+                "system wallpaper");
+    }
+
+    @Test
+    public void testOnFullBackup_existingSystemStage_sysChanged_backsUpNewSystemWallpaper()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockSystemWallpaperFileWithContents("system wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock that the system wallpaper was changed by the user.
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID + 1, NO_LOCK_WALLPAPER_ID);
+        mockSystemWallpaperFileWithContents("new system wallpaper");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+                "new system wallpaper");
+    }
+
+    @Test
+    public void testOnFullBackup_noExistingSystemStage_backsUpSystemWallpaper()
+            throws Exception {
+        mockSystemWallpaperFileWithContents("system wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+                "system wallpaper");
+    }
+
+    @Test
+    public void testOnFullBackup_lockWallpaperNotEligible_doesNotBackUpLockWallpaper()
+            throws Exception {
+        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(false);
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertThat(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).isPresent()).isFalse();
+    }
+
+    @Test
+    public void testOnFullBackup_existingLockStage_lockWallpaperRemovedByUser_NotBackUpOldStage()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock the ID of the lock wallpaper to indicate it's not set.
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertThat(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).isPresent()).isFalse();
+    }
+
+    @Test
+    public void testOnFullBackup_existingLockStage_lockWallpaperRemovedByUser_deletesExistingStage()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock the ID of the lock wallpaper to indicate it's not set.
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertThat(new File(mContext.getFilesDir(), LOCK_WALLPAPER_STAGE).exists()).isFalse();
+    }
+
+    @Test
+    public void testOnFullBackup_existingLockStage_noLockChange_backsUpAlreadyStagedFile()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockLockWallpaperFileWithContents("old lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // This new file should be ignored since the ID of the wallpaper did not change.
+        mockLockWallpaperFileWithContents("new lock wallpaper");
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+                "old lock wallpaper");
+    }
+
+    @Test
+    public void testOnFullBackup_existingLockStage_lockChanged_backsUpNewLockWallpaper()
+            throws Exception {
+        // Do a backup first so that a stage file is created.
+        mockLockWallpaperFileWithContents("old lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+        mWallpaperBackupAgent.mBackedUpFiles.clear();
+        // Mock that the lock wallpaper was changed by the user.
+        mockLockWallpaperFileWithContents("new lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID + 1);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+                "new lock wallpaper");
+    }
+
+    @Test
+    public void testOnFullBackup_noExistingLockStage_backsUpLockWallpaper()
+            throws Exception {
+        mockLockWallpaperFileWithContents("lock wallpaper");
+        mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+        mWallpaperBackupAgent.onFullBackup(mOutput);
+
+        assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+                "lock wallpaper");
+    }
+
+    @Test
+    public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
@@ -156,7 +341,7 @@
     }
 
     @Test
-    public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+    public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
@@ -172,7 +357,7 @@
     }
 
     @Test
-    public void updateWallpaperComponent_deviceNotInRestore_doesNotApply()
+    public void testUpdateWallpaperComponent_deviceNotInRestore_doesNotApply()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
@@ -188,7 +373,7 @@
     }
 
     @Test
-    public void updateWallpaperComponent_differentPackageInstalled_doesNotApply()
+    public void testUpdateWallpaperComponent_differentPackageInstalled_doesNotApply()
             throws IOException {
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
@@ -203,33 +388,48 @@
         verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
     }
 
-    private void mockUnbackedUpState() {
-        mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
-        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
-        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1))).thenReturn(-1);
+    private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
+        when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
+        when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
     }
 
-    private void mockBackedUpState() {
-        when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1)))
-                .thenReturn(TEST_SYSTEM_WALLPAPER_ID);
-        when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1)))
-                .thenReturn(TEST_LOCK_WALLPAPER_ID);
+    private File createTemporaryFileWithContentString(String contents) throws Exception {
+        File file = mTemporaryFolder.newFile();
+        try (FileOutputStream outputStream = new FileOutputStream(file)) {
+            outputStream.write(contents.getBytes());
+        }
+        return file;
     }
 
-    private void mockCurrentWallpapers(int systemWallpaperId, int lockWallpaperId) {
-        when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_SYSTEM), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(systemWallpaperId);
-        when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_LOCK), eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(lockWallpaperId);
-        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
-        when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
+    private void assertFileContentEquals(File file, String expected) throws Exception {
+        try (FileInputStream inputStream = new FileInputStream(file)) {
+            assertThat(new String(inputStream.readAllBytes())).isEqualTo(expected);
+        }
     }
 
-    private void mockSystemWallpaperReadyToBackUp() throws IOException {
-        // Create a system wallpaper file
-        mTemporaryFolder.newFile("wallpaper_orig");
-        // Create staging file to simulate he wallpaper being ready to back up
-        new File(mContext.getFilesDir(), "wallpaper-stage").createNewFile();
+    private Optional<File> getBackedUpFileOptional(String fileName) {
+        return mWallpaperBackupAgent.mBackedUpFiles.stream().filter(
+                file -> file.getName().equals(fileName)).findFirst();
+    }
+
+    private void mockWallpaperInfoFileWithContents(String contents) throws Exception {
+        File fakeInfoFile = createTemporaryFileWithContentString(contents);
+        when(mWallpaperManager.getWallpaperInfoFile()).thenReturn(
+                ParcelFileDescriptor.open(fakeInfoFile, MODE_READ_ONLY));
+    }
+
+    private void mockSystemWallpaperFileWithContents(String contents) throws Exception {
+        File fakeSystemWallpaperFile = createTemporaryFileWithContentString(contents);
+        when(mWallpaperManager.getWallpaperFile(eq(FLAG_SYSTEM), /* cropped = */
+                eq(false))).thenReturn(
+                ParcelFileDescriptor.open(fakeSystemWallpaperFile, MODE_READ_ONLY));
+    }
+
+    private void mockLockWallpaperFileWithContents(String contents) throws Exception {
+        File fakeLockWallpaperFile = createTemporaryFileWithContentString(contents);
+        when(mWallpaperManager.getWallpaperFile(eq(FLAG_LOCK), /* cropped = */
+                eq(false))).thenReturn(
+                ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY));
     }
 
     private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
@@ -243,21 +443,11 @@
         }
 
         @Override
-        protected File getWallpaperDir() {
-            return mWallpaperBaseDirectory;
-        }
-
-        @Override
         protected void backupFile(File file, FullBackupDataOutput data) {
             mBackedUpFiles.add(file);
         }
 
         @Override
-        public SharedPreferences getSharedPreferences(File file, int mode) {
-            return mSharedPreferences;
-        }
-
-        @Override
         boolean servicePackageExists(ComponentName comp) {
             return false;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 3fa0ab6..e6abc4c 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -391,7 +391,7 @@
     private boolean takeScreenshot() {
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+        screenshotHelper.takeScreenshot(
                 WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
                 new Handler(Looper.getMainLooper()), null);
         return true;
diff --git a/services/api/current.txt b/services/api/current.txt
index 3926b39..e66bf4d 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -180,8 +180,8 @@
     method @Nullable public String getPrimaryCpuAbi();
     method @Nullable public String getSeInfo();
     method @Nullable public String getSecondaryCpuAbi();
+    method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies();
     method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
-    method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
     method public boolean isApex();
     method public boolean isPrivileged();
     method public boolean isSystem();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 2c8bfeb..998c9c2 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -281,6 +281,8 @@
     // Pseudoname that we use for the Package Manager metadata "package".
     public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
 
+    public static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup";
+
     // Retry interval for clear/init when the transport is unavailable
     private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
 
@@ -309,7 +311,6 @@
     private static final String SERIAL_ID_FILE = "serial_id";
 
     private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
-    private static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup";
 
     private final @UserIdInt int mUserId;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index 7f0b56f..b20ff37 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -20,6 +20,7 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.Nullable;
@@ -56,7 +57,7 @@
     private static final boolean DEBUG = false;
     // List of system packages that are eligible for backup in non-system users.
     private static final Set<String> systemPackagesAllowedForAllUsers =
-            Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME);
+            Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME, WALLPAPER_PACKAGE);
 
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index db163dc..5985ce4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -34,6 +34,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
@@ -119,6 +120,7 @@
     private final VirtualDeviceParams mParams;
     private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
     private final IVirtualDeviceActivityListener mActivityListener;
+    private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
@@ -170,6 +172,7 @@
             OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
+            IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
         this(
@@ -184,6 +187,7 @@
                 onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
+                soundEffectListener,
                 runningAppsChangedCallback,
                 params);
     }
@@ -201,6 +205,7 @@
             OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
+            IVirtualDeviceSoundEffectListener soundEffectListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
         super(PermissionEnforcer.fromContext(context));
@@ -209,6 +214,7 @@
         mAssociationInfo = associationInfo;
         mPendingTrampolineCallback = pendingTrampolineCallback;
         mActivityListener = activityListener;
+        mSoundEffectListener = soundEffectListener;
         mRunningAppsChangedCallback = runningAppsChangedCallback;
         mOwnerUid = ownerUid;
         mDeviceId = deviceId;
@@ -937,6 +943,14 @@
                 Toast.LENGTH_LONG, mContext.getMainLooper());
     }
 
+    void playSoundEffect(int effectType) {
+        try {
+            mSoundEffectListener.onPlaySoundEffect(effectType);
+        } catch (RemoteException exception) {
+            Slog.w(TAG, "Unable to invoke sound effect listener", exception);
+        }
+    }
+
     /**
      * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if
      * the intent matches any filter notifying the DisplayPolicyController to abort the
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b0f2464..47ec80e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -30,6 +30,7 @@
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
@@ -222,7 +223,8 @@
                 String packageName,
                 int associationId,
                 @NonNull VirtualDeviceParams params,
-                @NonNull IVirtualDeviceActivityListener activityListener) {
+                @NonNull IVirtualDeviceActivityListener activityListener,
+                @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                     "createVirtualDevice");
@@ -246,7 +248,7 @@
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
                         associationInfo, token, callingUid, deviceId, cameraAccessController,
                         this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
-                        runningAppsChangedCallback, params);
+                        soundEffectListener, runningAppsChangedCallback, params);
                 mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
             }
@@ -364,6 +366,18 @@
             }
         }
 
+        @Override // Binder call
+        public void playSoundEffect(int deviceId, int effectType) {
+            VirtualDeviceImpl virtualDevice;
+            synchronized (mVirtualDeviceManagerLock) {
+                virtualDevice = mVirtualDevices.get(deviceId);
+            }
+
+            if (virtualDevice != null) {
+                virtualDevice.playSoundEffect(effectType);
+            }
+        }
+
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final UserHandle userHandle = getCallingUserHandle();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 661319f3..67f5f1b 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -44,6 +44,13 @@
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorProperties.ComponentInfo;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -1111,6 +1118,15 @@
             Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
             getVBMetaDigestInformation();
 
+            // Log to statsd
+            // TODO(b/264061957): For now, biometric system properties are always collected if users
+            //  share usage & diagnostics information. In the future, collect biometric system
+            //  properties only when transparency log verification of the target partitions fails
+            //  (e.g. when the system/vendor partitions have been changed) once the binary
+            //  transparency infrastructure is ready.
+            Slog.i(TAG, "Boot completed. Collecting biometric system properties.");
+            collectBiometricProperties();
+
             // to avoid the risk of holding up boot time, computations to measure APEX, Module, and
             // MBA digests are scheduled here, but only executed when the device is idle and plugged
             // in.
@@ -1207,6 +1223,141 @@
         }
     }
 
+    /**
+     * Convert a {@link FingerprintSensorProperties} sensor type to the corresponding enum to be
+     * logged.
+     *
+     * @param sensorType See {@link FingerprintSensorProperties}
+     * @return The enum to be logged
+     */
+    private int toFingerprintSensorType(@FingerprintSensorProperties.SensorType int sensorType) {
+        switch (sensorType) {
+            case FingerprintSensorProperties.TYPE_REAR:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_REAR;
+            case FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_ULTRASONIC;
+            case FingerprintSensorProperties.TYPE_UDFPS_OPTICAL:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_OPTICAL;
+            case FingerprintSensorProperties.TYPE_POWER_BUTTON:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_POWER_BUTTON;
+            case FingerprintSensorProperties.TYPE_HOME_BUTTON:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_HOME_BUTTON;
+            default:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert a {@link FaceSensorProperties} sensor type to the corresponding enum to be logged.
+     *
+     * @param sensorType See {@link FaceSensorProperties}
+     * @return The enum to be logged
+     */
+    private int toFaceSensorType(@FaceSensorProperties.SensorType int sensorType) {
+        switch (sensorType) {
+            case FaceSensorProperties.TYPE_RGB:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_RGB;
+            case FaceSensorProperties.TYPE_IR:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_IR;
+            default:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert a {@link SensorProperties} sensor strength to the corresponding enum to be logged.
+     *
+     * @param sensorStrength See {@link SensorProperties}
+     * @return The enum to be logged
+     */
+    private int toSensorStrength(@SensorProperties.Strength int sensorStrength) {
+        switch (sensorStrength) {
+            case SensorProperties.STRENGTH_CONVENIENCE:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_CONVENIENCE;
+            case SensorProperties.STRENGTH_WEAK:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_WEAK;
+            case SensorProperties.STRENGTH_STRONG:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_STRONG;
+            default:
+                return FrameworkStatsLog
+                        .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_UNKNOWN;
+        }
+    }
+
+    /**
+     * A helper function to log detailed biometric sensor properties to statsd.
+     *
+     * @param prop The biometric sensor properties to be logged
+     * @param modality The modality of the biometric (e.g. fingerprint, face) to be logged
+     * @param sensorType The specific type of the biometric to be logged
+     */
+    private void logBiometricProperties(SensorProperties prop, int modality, int sensorType) {
+        final int sensorId = prop.getSensorId();
+        final int sensorStrength = toSensorStrength(prop.getSensorStrength());
+
+        // Log data for each component
+        // Note: none of the component info is a device identifier since every device of a given
+        // model and build share the same biometric system info (see b/216195167)
+        for (ComponentInfo componentInfo : prop.getComponentInfo()) {
+            FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED,
+                    sensorId,
+                    modality,
+                    sensorType,
+                    sensorStrength,
+                    componentInfo.getComponentId().trim(),
+                    componentInfo.getHardwareVersion().trim(),
+                    componentInfo.getFirmwareVersion().trim(),
+                    componentInfo.getSerialNumber().trim(),
+                    componentInfo.getSoftwareVersion().trim());
+        }
+    }
+
+    private void collectBiometricProperties() {
+        PackageManager pm = mContext.getPackageManager();
+        FingerprintManager fpManager = null;
+        FaceManager faceManager = null;
+        if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            fpManager = mContext.getSystemService(FingerprintManager.class);
+        }
+        if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            faceManager = mContext.getSystemService(FaceManager.class);
+        }
+
+        if (fpManager != null) {
+            // Log data for each fingerprint sensor
+            for (FingerprintSensorPropertiesInternal propInternal :
+                    fpManager.getSensorPropertiesInternal()) {
+                final FingerprintSensorProperties prop =
+                        FingerprintSensorProperties.from(propInternal);
+                logBiometricProperties(prop,
+                        FrameworkStatsLog
+                                .BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FINGERPRINT,
+                        toFingerprintSensorType(prop.getSensorType()));
+            }
+        }
+
+        if (faceManager != null) {
+            // Log data for each face sensor
+            for (FaceSensorProperties prop : faceManager.getSensorProperties()) {
+                logBiometricProperties(prop,
+                        FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FACE,
+                        toFaceSensorType(prop.getSensorType()));
+            }
+        }
+    }
+
     private void getVBMetaDigestInformation() {
         mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
         Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4e5ce88..7b8ca91 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -3976,6 +3976,66 @@
         }
     }
 
+    /**
+     * Returns a string representation of the radio technology (network type)
+     * currently in use on the device.
+     * @param type for which network type is returned
+     * @return the name of the radio technology
+     *
+     */
+    private String getNetworkTypeName(@Annotation.NetworkType int type) {
+        switch (type) {
+            case TelephonyManager.NETWORK_TYPE_GPRS:
+                return "GPRS";
+            case TelephonyManager.NETWORK_TYPE_EDGE:
+                return "EDGE";
+            case TelephonyManager.NETWORK_TYPE_UMTS:
+                return "UMTS";
+            case TelephonyManager.NETWORK_TYPE_HSDPA:
+                return "HSDPA";
+            case TelephonyManager.NETWORK_TYPE_HSUPA:
+                return "HSUPA";
+            case TelephonyManager.NETWORK_TYPE_HSPA:
+                return "HSPA";
+            case TelephonyManager.NETWORK_TYPE_CDMA:
+                return "CDMA";
+            case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                return "CDMA - EvDo rev. 0";
+            case TelephonyManager.NETWORK_TYPE_EVDO_A:
+                return "CDMA - EvDo rev. A";
+            case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                return "CDMA - EvDo rev. B";
+            case TelephonyManager.NETWORK_TYPE_1xRTT:
+                return "CDMA - 1xRTT";
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                return "LTE";
+            case TelephonyManager.NETWORK_TYPE_EHRPD:
+                return "CDMA - eHRPD";
+            case TelephonyManager.NETWORK_TYPE_IDEN:
+                return "iDEN";
+            case TelephonyManager.NETWORK_TYPE_HSPAP:
+                return "HSPA+";
+            case TelephonyManager.NETWORK_TYPE_GSM:
+                return "GSM";
+            case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+                return "TD_SCDMA";
+            case TelephonyManager.NETWORK_TYPE_IWLAN:
+                return "IWLAN";
+
+            //TODO: This network type is marked as hidden because it is not a
+            // true network type and we are looking to remove it completely from the available list
+            // of network types.  Since this method is only used for logging, in the event that this
+            // network type is selected, the log will read as "Unknown."
+            //case TelephonyManager.NETWORK_TYPE_LTE_CA:
+            //    return "LTE_CA";
+
+            case TelephonyManager.NETWORK_TYPE_NR:
+                return "NR";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
     /** Returns a new PreciseCallState object with default values. */
     private static PreciseCallState createPreciseCallState() {
         return new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index ae50b23..2b43ef4 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -67,6 +68,7 @@
 import com.android.server.connectivity.Vpn;
 import com.android.server.connectivity.VpnProfileStore;
 import com.android.server.net.LockdownVpnTracker;
+import com.android.server.pm.UserManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -93,6 +95,7 @@
     private final INetworkManagementService mNMS;
     private final INetd mNetd;
     private final UserManager mUserManager;
+    private final int mMainUserId;
 
     @VisibleForTesting
     @GuardedBy("mVpns")
@@ -145,6 +148,12 @@
                 Vpn vpn, VpnProfile profile) {
             return new LockdownVpnTracker(context, handler, vpn,  profile);
         }
+
+        /** Get the main user on the device. */
+        public @UserIdInt int getMainUserId() {
+            // TODO(b/265785220): Change to use UserManager method instead.
+            return LocalServices.getService(UserManagerInternal.class).getMainUserId();
+        }
     }
 
     public VpnManagerService(Context context, Dependencies deps) {
@@ -159,6 +168,7 @@
         mNMS = mDeps.getINetworkManagementService();
         mNetd = mDeps.getNetd();
         mUserManager = mContext.getSystemService(UserManager.class);
+        mMainUserId = mDeps.getMainUserId();
         registerReceivers();
         log("VpnManagerService starting up");
     }
@@ -478,11 +488,12 @@
 
     @Override
     public boolean updateLockdownVpn() {
-        // Allow the system UID for the system server and for Settings.
+        // Allow the system UID for the system server and for Settings (from user 0 or main user).
         // Also, for unit tests, allow the process that ConnectivityService is running in.
         if (mDeps.getCallingUid() != Process.SYSTEM_UID
+                && mDeps.getCallingUid() != UserHandle.getUid(mMainUserId, Process.SYSTEM_UID)
                 && Binder.getCallingPid() != Process.myPid()) {
-            logw("Lockdown VPN only available to system process or AID_SYSTEM");
+            logw("Lockdown VPN only available to system process or AID_SYSTEM on main user");
             return false;
         }
 
@@ -697,7 +708,7 @@
                 intentFilter,
                 null /* broadcastPermission */,
                 mHandler);
-        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
+        mContext.createContextAsUser(UserHandle.of(mMainUserId), 0 /* flags */).registerReceiver(
                 mUserPresentReceiver,
                 new IntentFilter(Intent.ACTION_USER_PRESENT),
                 null /* broadcastPermission */,
@@ -735,6 +746,7 @@
 
             if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
                 onVpnLockdownReset();
+                return;
             }
 
             // UserId should be filled for below intents, check the existence.
@@ -795,7 +807,7 @@
             userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
             mVpns.put(userId, userVpn);
 
-            if (user.isPrimary() && isLockdownVpnEnabled()) {
+            if (userId == mMainUserId && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
             }
         }
@@ -910,15 +922,9 @@
     }
 
     private void onUserUnlocked(int userId) {
-        UserInfo user = mUserManager.getUserInfo(userId);
-        if (user == null) {
-            logw("Unlocked user doesn't exist. UserId: " + userId);
-            return;
-        }
-
         synchronized (mVpns) {
             // User present may be sent because of an unlock, which might mean an unlocked keystore.
-            if (user.isPrimary() && isLockdownVpnEnabled()) {
+            if (userId == mMainUserId && isLockdownVpnEnabled()) {
                 updateLockdownVpn();
             } else {
                 startAlwaysOnVpn(userId);
@@ -984,7 +990,7 @@
             }
 
             // Turn Always-on VPN off
-            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+            if (mLockdownEnabled && userId == mMainUserId) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 35b5f1b..c16314b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1491,13 +1491,7 @@
         Account[] sharedAccounts = getSharedAccountsAsUser(userId);
         if (sharedAccounts == null || sharedAccounts.length == 0) return;
         Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName());
-        int parentUserId = UserManager.isSplitSystemUser()
-                ? getUserManager().getUserInfo(userId).restrictedProfileParentId
-                : UserHandle.USER_SYSTEM;
-        if (parentUserId < 0) {
-            Log.w(TAG, "User " + userId + " has shared accounts, but no parent user");
-            return;
-        }
+        int parentUserId = UserHandle.USER_SYSTEM;
         for (Account sa : sharedAccounts) {
             if (ArrayUtils.contains(accounts, sa)) continue;
             // Account doesn't exist. Copy it now.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6719fdb..41437d1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1940,6 +1940,12 @@
                     ignoreForeground = true;
                 }
 
+                // Whether FGS-BG-start restriction is enabled for this service.
+                final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r);
+
+                // Whether to extend the SHORT_SERVICE time out.
+                boolean extendShortServiceTimeout = false;
+
                 int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
                 if (!ignoreForeground) {
                     if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
@@ -1955,41 +1961,77 @@
                                 "startForeground(SHORT_SERVICE) called on a service that's not"
                                 + " started.");
                     }
-                    // If the service is already an FGS, and the type is changing, then we
-                    // may need to do some extra work here.
-                    if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
-                        // TODO(short-service): Consider transitions:
-                        //   A. Short -> other types:
-                        //     Apply the BG restriction again. Don't just allow it.
-                        //     i.e. unless the app is in a situation where it's allowed to start
-                        //     a FGS, this transition shouldn't be allowed.
-                        //     ... But think about it more, there may be a case this should be
-                        //     allowed.
-                        //
-                        //     If the transition is allowed, stop the timeout.
-                        //     If the transition is _not_ allowed... keep the timeout?
-                        //
-                        //   B. Short -> Short:
-                        //     Allowed, but the timeout won't reset. The original timeout is used.
-                        //   C. Other -> short:
-                        //     This should always be allowed.
-                        //     A timeout should start.
 
-                        // For now, let's just disallow transition from / to SHORT_SERVICE.
-                        final boolean isNewTypeShortFgs =
-                                foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
-                        if (r.isShortFgs() != isNewTypeShortFgs) {
-                            // TODO(short-service): We should (probably) allow it.
-                            throw new IllegalArgumentException(
-                                    "setForeground(): Changing foreground service type from / to "
-                                    + " SHORT_SERVICE is now allowed");
+                    // Side note: If a valid short-service (which has to be "started"), happens to
+                    // also be bound, then we still _will_ apply a timeout, because it still has
+                    // to be stopped.
+
+                    // Calling startForeground on a SHORT_SERVICE will require some additional
+                    // checks.
+                    // A) SHORT_SERVICE -> another type.
+                    //    - This should be allowed only when the app could start another FGS.
+                    //    - When succeed, the timeout should stop.
+                    // B) SHORT_SERVICE -> SHORT_SERVICE
+                    //    - If the app could start an FGS, then this would extend the timeout.
+                    //    - Otherwise, it's basically a no-op.
+                    //    - If it's already timed out, we also throw.
+                    // Also,
+                    // C) another type -> SHORT_SERVICE
+                    //    - This will always be allowed.
+                    //    - Timeout will start.
+
+                    final boolean isOldTypeShortFgs = r.isShortFgs();
+                    final boolean isNewTypeShortFgs =
+                            foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+                    final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
+
+                    if (isOldTypeShortFgs || isNewTypeShortFgs) {
+                        if (DEBUG_SHORT_SERVICE) {
+                            Slog.i(TAG_SERVICE, String.format(
+                                    "FGS type changing from %x%s to %x: %s",
+                                    r.foregroundServiceType,
+                                    (isOldTypeShortFgsAndTimedOut ? "(timed out short FGS)" : ""),
+                                    foregroundServiceStartType,
+                                    r.toString()));
                         }
                     }
 
-                    // If a valid short-service (which has to be "started"), happens to
-                    // also be bound, then we still _will_ apply a timeout, because it still has
-                    // to be stopped.
-                    if (r.mStartForegroundCount == 0) {
+                    if (r.isForeground && isOldTypeShortFgs) {
+                        // If we get here, that means startForeground(SHORT_SERVICE) is called again
+                        // on a SHORT_SERVICE FGS.
+
+                        // See if the app could start an FGS or not.
+                        r.mAllowStartForeground = REASON_DENIED;
+                        setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+                                r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                BackgroundStartPrivileges.NONE,
+                                false /* isBindService */);
+
+                        final boolean fgsStartAllowed =
+                                !isBgFgsRestrictionEnabledForService
+                                        || (r.mAllowStartForeground != REASON_DENIED);
+
+                        if (fgsStartAllowed) {
+                            if (isNewTypeShortFgs) {
+                                // Only in this case, we extend the SHORT_SERVICE time out.
+                                extendShortServiceTimeout = true;
+                                if (DEBUG_SHORT_SERVICE) {
+                                    Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + r);
+                                }
+                            } else {
+                                // FGS type is changing from SHORT_SERVICE to another type when
+                                // an app is allowed to start FGS, so this will succeed.
+                                // The timeout will stop -- we actually don't cancel the handler
+                                // events, but they'll be ignored if the service type is not
+                                // SHORT_SERVICE.
+                                // TODO(short-service) Let's actaully cancel the handler events.
+                            }
+                        } else {
+                            // We catch this case later, in the
+                            // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+                        }
+
+                    } else if (r.mStartForegroundCount == 0) {
                         /*
                         If the service was started with startService(), not
                         startForegroundService(), and if startForeground() isn't called within
@@ -2032,6 +2074,7 @@
                                 BackgroundStartPrivileges.NONE,
                                 false /* isBindService */);
                     }
+
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
                     if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -2041,10 +2084,12 @@
                                         + r.shortInstanceName);
                     }
                     logFgsBackgroundStart(r);
-                    if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+                    if (r.mAllowStartForeground == REASON_DENIED
+                            && isBgFgsRestrictionEnabledForService) {
                         final String msg = "Service.startForeground() not allowed due to "
                                 + "mAllowStartForeground false: service "
-                                + r.shortInstanceName;
+                                + r.shortInstanceName
+                                + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
                         Slog.w(TAG, msg);
                         showFgsBgRestrictedNotificationLocked(r);
                         updateServiceForegroundLocked(psr, true);
@@ -2181,11 +2226,8 @@
                     mAm.notifyPackageUse(r.serviceInfo.packageName,
                             PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
 
-                    // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
-                    // already short-fgs.
-                    // In that case, because ShortFgsInfo is already set, this method
-                    // will be noop.
-                    maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
+                    maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r,
+                            extendShortServiceTimeout);
                 } else {
                     if (DEBUG_FOREGROUND_SERVICE) {
                         Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2982,17 +3024,21 @@
     /**
      * If {@code sr} is of a short-fgs, start a short-FGS timeout.
      */
-    private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+    private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr,
+            boolean extendTimeout) {
         if (!sr.isShortFgs()) {
             return;
         }
         if (DEBUG_SHORT_SERVICE) {
             Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
         }
-        if (sr.hasShortFgsInfo()) {
-            sr.getShortFgsInfo().update();
-        } else {
+
+        if (extendTimeout || !sr.hasShortFgsInfo()) {
             sr.setShortFgsInfo(SystemClock.uptimeMillis());
+        } else {
+            // We only (potentially) update the start command, start count, but not the timeout
+            // time.
+            sr.getShortFgsInfo().update();
         }
         unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
 
@@ -7562,7 +7608,8 @@
         if (!r.mLoggedInfoAllowStartForeground) {
             final String msg = "Background started FGS: "
                     + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
-                    + r.mInfoAllowStartForeground;
+                    + r.mInfoAllowStartForeground
+                    + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
             if (r.mAllowStartForeground != REASON_DENIED) {
                 if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
                         mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e962fb0..de87a0c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1606,8 +1606,6 @@
     // Encapsulates the global setting "hidden_api_blacklist_exemptions"
     final HiddenApiSettings mHiddenApiBlacklist;
 
-    final SdkSandboxSettings mSdkSandboxSettings;
-
     private final PlatformCompat mPlatformCompat;
 
     PackageManagerInternal mPackageManagerInt;
@@ -2324,53 +2322,6 @@
         }
     }
 
-    /**
-     * Handles settings related to the enforcement of SDK sandbox restrictions.
-     */
-    static class SdkSandboxSettings implements DeviceConfig.OnPropertiesChangedListener {
-
-        private final Context mContext;
-        private final Object mLock = new Object();
-
-        @GuardedBy("mLock")
-        private boolean mEnforceBroadcastReceiverRestrictions;
-
-        /**
-         * Property to enforce broadcast receiver restrictions for SDK sandbox processes. If the
-         * value of this property is {@code true}, the restrictions will be enforced.
-         */
-        public static final String ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS =
-                "enforce_broadcast_receiver_restrictions";
-
-        SdkSandboxSettings(Context context) {
-            mContext = context;
-        }
-
-        void registerObserver() {
-            synchronized (mLock) {
-                mEnforceBroadcastReceiverRestrictions = DeviceConfig.getBoolean(
-                        DeviceConfig.NAMESPACE_SDK_SANDBOX,
-                        ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
-                DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SDK_SANDBOX,
-                        mContext.getMainExecutor(), this);
-            }
-        }
-
-        @Override
-        public void onPropertiesChanged(DeviceConfig.Properties properties) {
-            synchronized (mLock) {
-                mEnforceBroadcastReceiverRestrictions = properties.getBoolean(
-                        ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
-            }
-        }
-
-        boolean isBroadcastReceiverRestrictionsEnforced() {
-            synchronized (mLock) {
-                return mEnforceBroadcastReceiverRestrictions;
-            }
-        }
-    }
-
     AppOpsManager getAppOpsManager() {
         if (mAppOpsManager == null) {
             mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
@@ -2414,7 +2365,6 @@
         mProcStartHandlerThread = null;
         mProcStartHandler = null;
         mHiddenApiBlacklist = null;
-        mSdkSandboxSettings = null;
         mFactoryTest = FACTORY_TEST_OFF;
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mInternal = new LocalService();
@@ -2539,7 +2489,6 @@
         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
 
         mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
-        mSdkSandboxSettings = new SdkSandboxSettings(mContext);
 
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
@@ -7438,11 +7387,12 @@
         if (shareDescription != null) {
             triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
         }
-        UserHandle callingUser = Binder.getCallingUserHandle();
         final long identity = Binder.clearCallingIdentity();
         try {
             // Send broadcast to shell to trigger bugreport using Bugreport API
-            mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
+            // Always start the shell process on the current user to ensure that
+            // the foreground user can see all bugreport notifications.
+            mContext.sendBroadcastAsUser(triggerShellBugreport, getCurrentUser().getUserHandle());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -7514,7 +7464,10 @@
      */
     @Override
     public boolean launchBugReportHandlerApp() {
-        if (!BugReportHandlerUtil.isBugReportHandlerEnabled(mContext)) {
+
+        Context currentUserContext = mContext.createContextAsUser(getCurrentUser().getUserHandle(),
+                /* flags= */ 0);
+        if (!BugReportHandlerUtil.isBugReportHandlerEnabled(currentUserContext)) {
             return false;
         }
 
@@ -7523,7 +7476,7 @@
         enforceCallingPermission(android.Manifest.permission.DUMP,
                 "launchBugReportHandlerApp");
 
-        return BugReportHandlerUtil.launchBugReportHandlerApp(mContext);
+        return BugReportHandlerUtil.launchBugReportHandlerApp(currentUserContext);
     }
 
     /**
@@ -8296,7 +8249,6 @@
         final boolean alwaysFinishActivities =
                 Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
         mHiddenApiBlacklist.registerObserver();
-        mSdkSandboxSettings.registerObserver();
         mPlatformCompat.registerContentObserver();
 
         mAppProfiler.retrieveSettings();
@@ -8467,10 +8419,7 @@
             // Enable home activity for system user, so that the system can always boot. We don't
             // do this when the system user is not setup since the setup wizard should be the one
             // to handle home activity in this case.
-            if (UserManager.isSplitSystemUser() &&
-                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                         Settings.Secure.USER_SETUP_COMPLETE, 0, currentUserId) != 0
-                    || SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
+            if (SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
                 t.traceBegin("enableHomeActivity");
                 ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
                 try {
@@ -13541,16 +13490,6 @@
             String callerFeatureId, String receiverId, IIntentReceiver receiver,
             IntentFilter filter, String permission, int userId, int flags) {
         enforceNotIsolatedCaller("registerReceiver");
-
-        // Allow Sandbox process to register only unexported receivers.
-        boolean unexported = (flags & Context.RECEIVER_NOT_EXPORTED) != 0;
-        if (mSdkSandboxSettings.isBroadcastReceiverRestrictionsEnforced()
-                && Process.isSdkSandboxUid(Binder.getCallingUid())
-                && !unexported) {
-            throw new SecurityException("SDK sandbox process not allowed to call "
-                + "registerReceiver");
-        }
-
         ArrayList<Intent> stickyIntents = null;
         ProcessRecord callerApp = null;
         final boolean visibleToInstantApps
@@ -13614,6 +13553,20 @@
                 }
             }
 
+            if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+                SdkSandboxManagerLocal sdkSandboxManagerLocal =
+                        LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+                if (sdkSandboxManagerLocal == null) {
+                    throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+                            + " whether SDK sandbox uid can register to broadcast receivers.");
+                }
+                if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+                        /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+                    throw new SecurityException("SDK sandbox not allowed to register receiver"
+                            + " with the given IntentFilter");
+                }
+            }
+
             // If the change is enabled, but neither exported or not exported is set, we need to log
             // an error so the consumer can know to explicitly set the value for their flag.
             // If the caller is registering for a sticky broadcast with a null receiver, we won't
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5b453b2..0327d16 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -380,7 +380,7 @@
         }
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
                 mBatteryUsageStatsStore);
-        mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map);
+        mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
     }
 
     public void publish() {
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index 2142ebc..63b14b8 100644
--- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java
+++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.AppOpsManager.OP_NONE;
+
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -66,29 +67,29 @@
      * Launches a bugreport-allowlisted app to handle a bugreport.
      *
      * <p>Allows a bug report handler app to take bugreports on the user's behalf. The handler can
-     * be predefined in the config, meant to be launched with the primary user. The user can
-     * override this with a different (or same) handler app on possibly a different user. This is
-     * useful for capturing bug reports from work profile, for instance.
-     *
-     * @param context Context
-     * @return true if there is a bugreport-allowlisted app to handle a bugreport, or false
+     * be predefined in the config, meant to be launched with the current foreground user. The user
+     * can override this with a different (or same) handler app on possibly a different
+     * user profile. This is useful for capturing bug reports from work profile, for instance.
+     * @param userContext Context of the current foreground user
+     * @return true if there is a bugreport-allow-listed app to handle a bugreport, or false
      * otherwise
      */
-    static boolean launchBugReportHandlerApp(Context context) {
-        if (!isBugReportHandlerEnabled(context)) {
+    static boolean launchBugReportHandlerApp(Context userContext) {
+        if (!isBugReportHandlerEnabled(userContext)) {
             return false;
         }
 
-        String handlerApp = getCustomBugReportHandlerApp(context);
+        String handlerApp = getCustomBugReportHandlerApp(userContext);
         if (isShellApp(handlerApp)) {
             return false;
         }
 
-        int handlerUser = getCustomBugReportHandlerUser(context);
+        int handlerUser = getCustomBugReportHandlerUser(userContext);
         if (!isValidBugReportHandlerApp(handlerApp)) {
-            handlerApp = getDefaultBugReportHandlerApp(context);
-            handlerUser = UserHandle.USER_SYSTEM;
-        } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
+            handlerApp = getDefaultBugReportHandlerApp(userContext);
+            handlerUser = userContext.getUserId();
+        } else if (getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
+                .isEmpty()) {
             // It looks like the settings are outdated, reset outdated settings.
             //
             // i.e.
@@ -97,21 +98,23 @@
             // === RESULT ===
             // The chosen bugreport handler app is outdated because the profile is removed,
             // so reset the chosen app and profile
-            handlerApp = getDefaultBugReportHandlerApp(context);
-            handlerUser = UserHandle.USER_SYSTEM;
-            resetCustomBugreportHandlerAppAndUser(context);
+            handlerApp = getDefaultBugReportHandlerApp(userContext);
+            handlerUser = userContext.getUserId();
+            resetCustomBugreportHandlerAppAndUser(userContext);
         }
 
         if (isShellApp(handlerApp) || !isValidBugReportHandlerApp(handlerApp)
-                || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
+                || getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
+                .isEmpty()) {
             return false;
         }
 
-        if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) {
+        if (getBugReportHandlerAppResponseReceivers(userContext, handlerApp, handlerUser)
+                .isEmpty()) {
             // Just try to launch bugreport handler app to handle bugreport request
             // because the bugreport handler app is old and not support to provide response to
             // let BugReportHandlerUtil know it is available or not.
-            launchBugReportHandlerApp(context, handlerApp, handlerUser);
+            launchBugReportHandlerApp(userContext, handlerApp, handlerUser);
             return true;
         }
 
@@ -124,7 +127,7 @@
         try {
             // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
             // let BugreportHandlerResponseBroadcastReceiver know the handler app is available.
-            context.sendOrderedBroadcastAsUser(intent,
+            userContext.sendOrderedBroadcastAsUser(intent,
                     UserHandle.of(handlerUser),
                     android.Manifest.permission.DUMP,
                     OP_NONE, /* options= */ null,
@@ -166,13 +169,14 @@
 
     private static String getCustomBugReportHandlerApp(Context context) {
         // Get the package of custom bugreport handler app
-        return Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP);
+        return Settings.Secure.getStringForUser(context.getContentResolver(),
+                Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, context.getUserId());
     }
 
     private static int getCustomBugReportHandlerUser(Context context) {
-        return Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL);
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL,
+                context.getUserId());
     }
 
     private static boolean isShellApp(String app) {
@@ -219,11 +223,11 @@
     private static void resetCustomBugreportHandlerAppAndUser(Context context) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            Settings.Global.putString(context.getContentResolver(),
-                    Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
+            Settings.Secure.putString(context.getContentResolver(),
+                    Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
                     getDefaultBugReportHandlerApp(context));
-            Settings.Global.putInt(context.getContentResolver(),
-                    Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_SYSTEM);
+            Settings.Secure.putInt(context.getContentResolver(),
+                    Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, context.getUserId());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index d2fb7b5..85de637 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -83,11 +83,13 @@
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -124,13 +126,6 @@
     ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
             String name, int userId, boolean stable) {
         mService.enforceNotIsolatedCaller("getContentProvider");
-        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
-            // TODO(b/226318628): for sdk sandbox processes only allow accessing CPs registered by
-            //  the WebView apk.
-            Slog.w(TAG, "Sdk sandbox process " + Binder.getCallingUid()
-                    + " is accessing content provider " + name
-                    + ". This access will most likely be blocked in the future");
-        }
         if (caller == null) {
             String msg = "null IApplicationThread when getting content provider " + name;
             Slog.w(TAG, msg);
@@ -255,6 +250,7 @@
                 if (r != null && cpr.canRunHere(r)) {
                     checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
                             cpr.name.flattenToShortString(), startTime);
+                    enforceContentProviderRestrictionsForSdkSandbox(cpi);
 
                     // This provider has been published or is in the process
                     // of being published...  but it is also allowed to run
@@ -447,6 +443,7 @@
                     // info and allow the caller to instantiate it.  Only do
                     // this if the provider is the same user as the caller's
                     // process, or can run as root (so can be in any process).
+                    enforceContentProviderRestrictionsForSdkSandbox(cpi);
                     return cpr.newHolder(null, true);
                 }
 
@@ -589,6 +586,8 @@
                 // Return a holder instance even if we are waiting for the publishing of the
                 // provider, client will check for the holder.provider to see if it needs to wait
                 // for it.
+                //todo(b/265965249) Need to perform cleanup before calling enforce method here
+                enforceContentProviderRestrictionsForSdkSandbox(cpi);
                 return cpr.newHolder(conn, false);
             }
         }
@@ -650,6 +649,7 @@
                     + " caller=" + callerName + "/" + Binder.getCallingUid());
             return null;
         }
+        enforceContentProviderRestrictionsForSdkSandbox(cpi);
         return cpr.newHolder(conn, false);
     }
 
@@ -1230,6 +1230,7 @@
             appName = r.toString();
         }
 
+        enforceContentProviderRestrictionsForSdkSandbox(cpi);
         return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
                 userId, checkUser, appName);
     }
@@ -1998,6 +1999,26 @@
         }
     }
 
+    // Binder.clearCallingIdentity() shouldn't be called before this method
+    // as Binder should have its original callingUid for the check
+    private void enforceContentProviderRestrictionsForSdkSandbox(ProviderInfo cpi) {
+        if (!Process.isSdkSandboxUid(Binder.getCallingUid())) {
+            return;
+        }
+        final SdkSandboxManagerLocal sdkSandboxManagerLocal =
+                LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+        if (sdkSandboxManagerLocal == null) {
+            throw new IllegalStateException("SdkSandboxManagerLocal not found "
+                    + "when checking whether SDK sandbox uid may "
+                    + "access the contentprovider.");
+        }
+        if (!sdkSandboxManagerLocal
+                .canAccessContentProviderFromSdkSandbox(cpi)) {
+            throw new SecurityException(
+                    "SDK sandbox uid may not access contentprovider " + cpi.name);
+        }
+    }
+
     /**
      * There are three ways to call this:
      *  - no provider specified: dump all the providers
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 874fda3..43075bc 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -22,16 +22,21 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerWhitelistManager;
@@ -40,6 +45,7 @@
 import android.os.RemoteException;
 import android.os.TransactionTooLargeException;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -56,6 +62,13 @@
 public final class PendingIntentRecord extends IIntentSender.Stub {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentRecord" : TAG_AM;
 
+    /** If enabled BAL are prevented by default in applications targeting U and later. */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
+    private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
+            "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+
     public static final int FLAG_ACTIVITY_SENDER = 1 << 0;
     public static final int FLAG_BROADCAST_SENDER = 1 << 1;
     public static final int FLAG_SERVICE_SENDER = 1 << 2;
@@ -357,16 +370,34 @@
                 : BackgroundStartPrivileges.NONE;
     }
 
+    private static boolean isDefaultRescindBalPrivilegesFromPendingIntentSenderEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER,
+                false); // assume false if the property is unknown
+    }
+
     /**
      * Default {@link BackgroundStartPrivileges} to be used if the intent sender has not made an
      * explicit choice.
      *
      * @hide
      */
-    public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(int callingUid) {
-        // TODO: In the next step this will return ALLOW_FGS instead, if the app that sent the
-        // PendingIntent is targeting Android U
-        return BackgroundStartPrivileges.ALLOW_BAL;
+    @RequiresPermission(
+            allOf = {
+                    android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                    android.Manifest.permission.LOG_COMPAT_CHANGE
+            })
+    public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
+            int callingUid) {
+        boolean isFlagEnabled = isDefaultRescindBalPrivilegesFromPendingIntentSenderEnabled();
+        boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled(
+                DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid);
+        if (isFlagEnabled && isChangeEnabledForApp) {
+            return BackgroundStartPrivileges.ALLOW_FGS;
+        } else {
+            return BackgroundStartPrivileges.ALLOW_BAL;
+        }
     }
 
     @Deprecated
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index f61737e..3df060b 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -555,12 +555,6 @@
                 // This user is already stopping, doesn't count.
                 continue;
             }
-            if (userId == UserHandle.USER_SYSTEM) {
-                // We only count system user as running when it is not a pure system user.
-                if (UserInfo.isSystemOnly(userId)) {
-                    continue;
-                }
-            }
             runningUsers.add(userId);
         }
         return runningUsers;
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 7a6603d..a5651bf 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -95,9 +95,7 @@
                 R.layout.user_switching_dialog, null);
 
         String viewMessage = null;
-        if (UserManager.isSplitSystemUser() && mNewUser.id == UserHandle.USER_SYSTEM) {
-            viewMessage = res.getString(R.string.user_logging_out_message, mOldUser.name);
-        } else if (UserManager.isDeviceInDemoMode(mContext)) {
+        if (UserManager.isDeviceInDemoMode(mContext)) {
             if (mOldUser.isDemo()) {
                 viewMessage = res.getString(R.string.demo_restarting_message);
             } else {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 908cb3f..684d6a0 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -17,6 +17,8 @@
 package com.android.server.app;
 
 import static android.Manifest.permission.MANAGE_GAME_ACTIVITY;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -34,7 +36,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.games.CreateGameSessionRequest;
@@ -51,7 +52,6 @@
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.WindowManager;
 import android.window.ScreenCapture;
 
 import com.android.internal.annotations.GuardedBy;
@@ -61,6 +61,7 @@
 import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
@@ -863,8 +864,6 @@
                 Slog.w(TAG, "Could not get bitmap for id: " + taskId);
                 callback.complete(GameScreenshotResult.createInternalErrorResult());
             } else {
-                final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
-                        bitmap);
                 final RunningTaskInfo runningTaskInfo =
                         mGameTaskInfoProvider.getRunningTaskInfo(taskId);
                 if (runningTaskInfo == null) {
@@ -879,11 +878,17 @@
                         callback.complete(GameScreenshotResult.createSuccessResult());
                     }
                 };
-                mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
-                        mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
-                        WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
-                        BackgroundThread.getHandler(),
-                        completionConsumer);
+                ScreenshotRequest request = new ScreenshotRequest.Builder(
+                        TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(gameSessionRecord.getComponentName())
+                        .setTaskId(taskId)
+                        .setUserId(mUserHandle.getIdentifier())
+                        .setBitmap(bitmap)
+                        .setBoundsOnScreen(crop)
+                        .setInsets(Insets.NONE)
+                        .build();
+                mScreenshotHelper.takeScreenshot(
+                        request, BackgroundThread.getHandler(), completionConsumer);
             }
         });
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c794b04..cb98c66 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3782,7 +3782,7 @@
         VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
 
         sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
-                index/*val1*/, flags/*val2*/, callingPackage));
+                index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser()));
 
         vgs.setVolumeIndex(index, flags);
 
@@ -3803,7 +3803,7 @@
 
     @Nullable
     private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) {
-        for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
+        for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
             if (avg.getId() == volumeGroupId) {
                 return avg;
             }
@@ -3819,14 +3819,15 @@
         super.getVolumeIndexForAttributes_enforcePermission();
 
         Objects.requireNonNull(attr, "attr must not be null");
-        final int volumeGroup =
-                AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
-                        attr, /* fallbackOnDefault= */false);
-        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
-            throw new IllegalArgumentException("No volume group for attributes " + attr);
+        synchronized (VolumeStreamState.class) {
+            int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+                    attr, /* fallbackOnDefault= */false);
+            if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+                throw new IllegalArgumentException("No volume group for attributes " + attr);
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+            return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex();
         }
-        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
-        return vgs.getVolumeIndex();
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -5922,7 +5923,7 @@
         mSoundDoseHelper.restoreMusicActiveMs();
         mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG);
 
-        readVolumeGroupsSettings();
+        readVolumeGroupsSettings(userSwitch);
 
         if (DEBUG_VOL) {
             Log.d(TAG, "Restoring device volume behavior");
@@ -7311,6 +7312,7 @@
             try {
                 // if no valid attributes, this volume group is not controllable, throw exception
                 ensureValidAttributes(avg);
+                sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
             } catch (IllegalArgumentException e) {
                 // Volume Groups without attributes are not controllable through set/get volume
                 // using attributes. Do not append them.
@@ -7319,11 +7321,10 @@
                 }
                 continue;
             }
-            sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
         }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.applyAllVolumes();
+            vgs.applyAllVolumes(/* userSwitch= */ false);
         }
     }
 
@@ -7336,14 +7337,22 @@
         }
     }
 
-    private void readVolumeGroupsSettings() {
-        if (DEBUG_VOL) {
-            Log.v(TAG, "readVolumeGroupsSettings");
-        }
-        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
-            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.readSettings();
-            vgs.applyAllVolumes();
+    private void readVolumeGroupsSettings(boolean userSwitch) {
+        synchronized (mSettingsLock) {
+            synchronized (VolumeStreamState.class) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch);
+                }
+                for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+                    VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+                    // as for STREAM_MUSIC, preserve volume from one user to the next.
+                    if (!(userSwitch && vgs.isMusic())) {
+                        vgs.clearIndexCache();
+                        vgs.readSettings();
+                    }
+                    vgs.applyAllVolumes(userSwitch);
+                }
+            }
         }
     }
 
@@ -7354,7 +7363,7 @@
         }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.applyAllVolumes();
+            vgs.applyAllVolumes(false/*userSwitch*/);
         }
     }
 
@@ -7367,17 +7376,24 @@
         }
     }
 
+    private static boolean isCallStream(int stream) {
+        return stream == AudioSystem.STREAM_VOICE_CALL
+                || stream == AudioSystem.STREAM_BLUETOOTH_SCO;
+    }
+
     // NOTE: Locking order for synchronized objects related to volume management:
     //  1     mSettingsLock
-    //  2       VolumeGroupState.class
+    //  2       VolumeStreamState.class
     private class VolumeGroupState {
         private final AudioVolumeGroup mAudioVolumeGroup;
         private final SparseIntArray mIndexMap = new SparseIntArray(8);
         private int mIndexMin;
         private int mIndexMax;
-        private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+        private boolean mHasValidStreamType = false;
         private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
         private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes();
+        private boolean mIsMuted = false;
+        private final String mSettingName;
 
         // No API in AudioSystem to get a device from strategy or from attributes.
         // Need a valid public stream type to use current API getDeviceForStream
@@ -7391,20 +7407,22 @@
                 Log.v(TAG, "VolumeGroupState for " + avg.toString());
             }
             // mAudioAttributes is the default at this point
-            for (final AudioAttributes aa : avg.getAudioAttributes()) {
+            for (AudioAttributes aa : avg.getAudioAttributes()) {
                 if (!aa.equals(mAudioAttributes)) {
                     mAudioAttributes = aa;
                     break;
                 }
             }
-            final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            String streamSettingName = "";
             if (streamTypes.length != 0) {
                 // Uses already initialized MIN / MAX if a stream type is attached to group
-                mLegacyStreamType = streamTypes[0];
-                for (final int streamType : streamTypes) {
+                for (int streamType : streamTypes) {
                     if (streamType != AudioSystem.STREAM_DEFAULT
                             && streamType < AudioSystem.getNumStreamTypes()) {
                         mPublicStreamType = streamType;
+                        mHasValidStreamType = true;
+                        streamSettingName = System.VOLUME_SETTINGS_INT[mPublicStreamType];
                         break;
                     }
                 }
@@ -7414,10 +7432,10 @@
                 mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
                 mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
             } else {
-                Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+                throw new IllegalArgumentException("volume group: " + mAudioVolumeGroup.name()
                         + " has neither valid attributes nor valid stream types assigned");
-                return;
             }
+            mSettingName = !streamSettingName.isEmpty() ? streamSettingName : ("volume_" + name());
             // Load volume indexes from data base
             readSettings();
         }
@@ -7430,40 +7448,101 @@
             return mAudioVolumeGroup.name();
         }
 
+        /**
+         * Volume group with non null minimum index are considered as non mutable, thus
+         * bijectivity is broken with potential associated stream type.
+         * VOICE_CALL stream has minVolumeIndex > 0  but can be muted directly by an
+         * app that has MODIFY_PHONE_STATE permission.
+         */
+        private boolean isVssMuteBijective(int stream) {
+            return isStreamAffectedByMute(stream)
+                    && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10)
+                    && (getMinIndex() == 0 || isCallStream(stream));
+        }
+
+        private boolean isMutable() {
+            return mIndexMin == 0 || (mHasValidStreamType && isVssMuteBijective(mPublicStreamType));
+        }
+        /**
+         * Mute/unmute the volume group
+         * @param muted the new mute state
+         */
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        public boolean mute(boolean muted) {
+            if (!isMutable()) {
+                // Non mutable volume group
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "invalid mute on unmutable volume group " + name());
+                }
+                return false;
+            }
+            boolean changed = (mIsMuted != muted);
+            // As for VSS, mute shall apply minIndex to all devices found in IndexMap and default.
+            if (changed) {
+                mIsMuted = muted;
+                applyAllVolumes(false /*userSwitch*/);
+            }
+            return changed;
+        }
+
+        public boolean isMuted() {
+            return mIsMuted;
+        }
+
         public int getVolumeIndex() {
-            return getIndex(getDeviceForVolume());
+            synchronized (VolumeStreamState.class) {
+                return getIndex(getDeviceForVolume());
+            }
         }
 
         public void setVolumeIndex(int index, int flags) {
-            if (mUseFixedVolume) {
-                return;
+            synchronized (VolumeStreamState.class) {
+                if (mUseFixedVolume) {
+                    return;
+                }
+                setVolumeIndex(index, getDeviceForVolume(), flags);
             }
-            setVolumeIndex(index, getDeviceForVolume(), flags);
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private void setVolumeIndex(int index, int device, int flags) {
-            // Set the volume index
-            setVolumeIndexInt(index, device, flags);
-
-            // Update local cache
-            mIndexMap.put(device, index);
-
-            // update data base - post a persist volume group msg
-            sendMsg(mAudioHandler,
-                    MSG_PERSIST_VOLUME_GROUP,
-                    SENDMSG_QUEUE,
-                    device,
-                    0,
-                    this,
-                    PERSIST_DELAY);
+            // Update cache & persist (muted by volume 0 shall be persisted)
+            updateVolumeIndex(index, device);
+            // setting non-zero volume for a muted stream unmutes the stream and vice versa,
+            boolean changed = mute(index == 0);
+            if (!changed) {
+                // Set the volume index only if mute operation is a no-op
+                index = getValidIndex(index);
+                setVolumeIndexInt(index, device, flags);
+            }
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        public void updateVolumeIndex(int index, int device) {
+            // Filter persistency if already exist and the index has not changed
+            if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) {
+                // Update local cache
+                mIndexMap.put(device, getValidIndex(index));
+
+                // update data base - post a persist volume group msg
+                sendMsg(mAudioHandler,
+                        MSG_PERSIST_VOLUME_GROUP,
+                        SENDMSG_QUEUE,
+                        device,
+                        0,
+                        this,
+                        PERSIST_DELAY);
+            }
+        }
+
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private void setVolumeIndexInt(int index, int device, int flags) {
             // Reflect mute state of corresponding stream by forcing index to 0 if muted
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
             // index is just set to 0 to repect BT requirements
-            if (mStreamStates[mPublicStreamType].isFullyMuted()) {
+            if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
+                    && mStreamStates[mPublicStreamType].isFullyMuted()) {
                 index = 0;
             } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) {
                 index = 1;
@@ -7472,18 +7551,16 @@
             AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
         }
 
-        public int getIndex(int device) {
-            synchronized (VolumeGroupState.class) {
-                int index = mIndexMap.get(device, -1);
-                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
-                return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
-            }
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        private int getIndex(int device) {
+            int index = mIndexMap.get(device, -1);
+            // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+            return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
         }
 
-        public boolean hasIndexForDevice(int device) {
-            synchronized (VolumeGroupState.class) {
-                return (mIndexMap.get(device, -1) != -1);
-            }
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        private boolean hasIndexForDevice(int device) {
+            return (mIndexMap.get(device, -1) != -1);
         }
 
         public int getMaxIndex() {
@@ -7494,55 +7571,108 @@
             return mIndexMin;
         }
 
-        private boolean isValidLegacyStreamType() {
-            return (mLegacyStreamType != AudioSystem.STREAM_DEFAULT)
-                    && (mLegacyStreamType < mStreamStates.length);
+        private boolean isValidStream(int stream) {
+            return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length);
         }
 
-        public void applyAllVolumes() {
-            synchronized (VolumeGroupState.class) {
-                int deviceForStream = AudioSystem.DEVICE_NONE;
-                int volumeIndexForStream = 0;
-                if (isValidLegacyStreamType()) {
-                    // Prevent to apply settings twice when group is associated to public stream
-                    deviceForStream = getDeviceForStream(mLegacyStreamType);
-                    volumeIndexForStream = getStreamVolume(mLegacyStreamType);
-                }
+        public boolean isMusic() {
+            return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC;
+        }
+
+        public void applyAllVolumes(boolean userSwitch) {
+            String caller = "from vgs";
+            synchronized (VolumeStreamState.class) {
                 // apply device specific volumes first
-                int index;
                 for (int i = 0; i < mIndexMap.size(); i++) {
-                    final int device = mIndexMap.keyAt(i);
+                    int device = mIndexMap.keyAt(i);
+                    int index = mIndexMap.valueAt(i);
+                    boolean synced = false;
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
-                        index = mIndexMap.valueAt(i);
-                        if (device == deviceForStream && volumeIndexForStream == index) {
-                            continue;
+                        for (int stream : getLegacyStreamTypes()) {
+                            if (isValidStream(stream)) {
+                                boolean streamMuted = mStreamStates[stream].mIsMuted;
+                                int deviceForStream = getDeviceForStream(stream);
+                                int indexForStream =
+                                        (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10;
+                                if (device == deviceForStream) {
+                                    if (indexForStream == index && (isMuted() == streamMuted)
+                                            && isVssMuteBijective(stream)) {
+                                        synced = true;
+                                        continue;
+                                    }
+                                    if (indexForStream != index) {
+                                        mStreamStates[stream].setIndex(index * 10, device, caller,
+                                                true /*hasModifyAudioSettings*/);
+                                    }
+                                    if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+                                        mStreamStates[stream].mute(isMuted());
+                                    }
+                                }
+                            }
                         }
-                        if (DEBUG_VOL) {
-                            Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
-                                    + mAudioVolumeGroup.name() + " and device "
-                                    + AudioSystem.getOutputDeviceName(device));
+                        if (!synced) {
+                            if (DEBUG_VOL) {
+                                Log.d(TAG, "applyAllVolumes: apply index " + index + ", group "
+                                        + mAudioVolumeGroup.name() + " and device "
+                                        + AudioSystem.getOutputDeviceName(device));
+                            }
+                            setVolumeIndexInt(isMuted() ? 0 : index, device, 0 /*flags*/);
                         }
-                        setVolumeIndexInt(index, device, 0 /*flags*/);
                     }
                 }
                 // apply default volume last: by convention , default device volume will be used
                 // by audio policy manager if no explicit volume is present for a given device type
-                index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
-                if (DEBUG_VOL) {
-                    Log.v(TAG, "applyAllVolumes: restore default device index " + index
-                            + " for group " + mAudioVolumeGroup.name());
-                }
-                if (isValidLegacyStreamType()) {
-                    int defaultStreamIndex = (mStreamStates[mLegacyStreamType]
-                            .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
-                    if (defaultStreamIndex == index) {
-                        return;
+                int index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+                boolean synced = false;
+                int deviceForVolume = getDeviceForVolume();
+                boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0);
+                for (int stream : getLegacyStreamTypes()) {
+                    if (isValidStream(stream)) {
+                        boolean streamMuted = mStreamStates[stream].mIsMuted;
+                        int defaultStreamIndex = (mStreamStates[stream].getIndex(
+                                        AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
+                        if (forceDeviceSync) {
+                            mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller,
+                                    true /*hasModifyAudioSettings*/);
+                        }
+                        if (defaultStreamIndex == index && (isMuted() == streamMuted)
+                                && isVssMuteBijective(stream)) {
+                            synced = true;
+                            continue;
+                        }
+                        if (defaultStreamIndex != index) {
+                            mStreamStates[stream].setIndex(
+                                    index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
+                                    true /*hasModifyAudioSettings*/);
+                        }
+                        if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+                            mStreamStates[stream].mute(isMuted());
+                        }
                     }
                 }
-                setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+                if (!synced) {
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "applyAllVolumes: apply default device index " + index
+                                + ", group " + mAudioVolumeGroup.name());
+                    }
+                    setVolumeIndexInt(
+                            isMuted() ? 0 : index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+                }
+                if (forceDeviceSync) {
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "applyAllVolumes: forceDeviceSync index " + index
+                                + ", device " + AudioSystem.getOutputDeviceName(deviceForVolume)
+                                + ", group " + mAudioVolumeGroup.name());
+                    }
+                    setVolumeIndexInt(isMuted() ? 0 : index, deviceForVolume, 0);
+                }
             }
         }
 
+        public void clearIndexCache() {
+            mIndexMap.clear();
+        }
+
         private void persistVolumeGroup(int device) {
             if (mUseFixedVolume) {
                 return;
@@ -7556,16 +7686,14 @@
             boolean success = mSettings.putSystemIntForUser(mContentResolver,
                     getSettingNameForDevice(device),
                     getIndex(device),
-                    UserHandle.USER_CURRENT);
+                    isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
             if (!success) {
                 Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
             }
         }
 
         public void readSettings() {
-            synchronized (VolumeGroupState.class) {
-                // First clear previously loaded (previous user?) settings
-                mIndexMap.clear();
+            synchronized (VolumeStreamState.class) {
                 // force maximum volume on all streams if fixed volume property is set
                 if (mUseFixedVolume) {
                     mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -7580,7 +7708,8 @@
                     int index;
                     String name = getSettingNameForDevice(device);
                     index = mSettings.getSystemIntForUser(
-                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+                            mContentResolver, name, defaultIndex,
+                            isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
                     if (index == -1) {
                         continue;
                     }
@@ -7598,6 +7727,7 @@
             }
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private int getValidIndex(int index) {
             if (index < mIndexMin) {
                 return mIndexMin;
@@ -7608,15 +7738,17 @@
         }
 
         public @NonNull String getSettingNameForDevice(int device) {
-            final String suffix = AudioSystem.getOutputDeviceName(device);
+            String suffix = AudioSystem.getOutputDeviceName(device);
             if (suffix.isEmpty()) {
-                return mAudioVolumeGroup.name();
+                return mSettingName;
             }
-            return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+            return mSettingName + "_" + AudioSystem.getOutputDeviceName(device);
         }
 
         private void dump(PrintWriter pw) {
             pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+            pw.print("   Muted: ");
+            pw.println(mIsMuted);
             pw.print("   Min: ");
             pw.println(mIndexMin);
             pw.print("   Max: ");
@@ -7626,9 +7758,9 @@
                 if (i > 0) {
                     pw.print(", ");
                 }
-                final int device = mIndexMap.keyAt(i);
+                int device = mIndexMap.keyAt(i);
                 pw.print(Integer.toHexString(device));
-                final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+                String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
                         : AudioSystem.getOutputDeviceName(device);
                 if (!deviceName.isEmpty()) {
                     pw.print(" (");
@@ -7641,7 +7773,7 @@
             pw.println();
             pw.print("   Devices: ");
             int n = 0;
-            final int devices = getDeviceForVolume();
+            int devices = getDeviceForVolume();
             for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
                 if ((devices & device) == device) {
                     if (n++ > 0) {
@@ -7650,6 +7782,10 @@
                     pw.print(AudioSystem.getOutputDeviceName(device));
                 }
             }
+            pw.println();
+            pw.print("   Streams: ");
+            Arrays.stream(getLegacyStreamTypes())
+                    .forEach(stream -> pw.print(AudioSystem.streamToString(stream) + " "));
         }
     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 42fe9d8..a589313 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -64,10 +64,27 @@
      */
     public static final int FLAG_EMULATED_ONLY = 1 << 2;
 
+    /**
+     * This flag indicates that the corresponding state should be automatically canceled when the
+     * requesting app is no longer on top. The app is considered not on top when (1) the top
+     * activity in the system is from a different app, (2) the device is in sleep mode, or
+     * (3) the keyguard shows up.
+     */
+    public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3;
+
+    /**
+     * This flag indicates that the corresponding state should be disabled when the device is
+     * overheating and reaching the critical status.
+     */
+    public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+
     /** @hide */
     @IntDef(prefix = {"FLAG_"}, flag = true, value = {
             FLAG_CANCEL_OVERRIDE_REQUESTS,
-            FLAG_APP_INACCESSIBLE
+            FLAG_APP_INACCESSIBLE,
+            FLAG_EMULATED_ONLY,
+            FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+            FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeviceStateFlags {}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c856cab..064cd2d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -33,6 +33,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.TaskStackListener;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateManager;
@@ -176,6 +177,12 @@
     @NonNull
     private final SystemPropertySetter mSystemPropertySetter;
 
+    @VisibleForTesting
+    TaskStackListener mOverrideRequestTaskStackListener = new OverrideRequestTaskStackListener();
+    @VisibleForTesting
+    ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
+            new OverrideRequestScreenObserver();
+
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, DeviceStatePolicy.Provider
                 .fromResources(context.getResources())
@@ -215,6 +222,9 @@
             readStatesAvailableForRequestFromApps();
             mFoldedDeviceStates = readFoldedStates();
         }
+
+        mActivityTaskManagerInternal.registerTaskStackListener(mOverrideRequestTaskStackListener);
+        mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver);
     }
 
     @VisibleForTesting
@@ -842,9 +852,7 @@
      * @param state state that is being requested.
      */
     private void assertCanRequestDeviceState(int callingPid, int state) {
-        final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
-        if (topApp == null || topApp.getPid() != callingPid
-                || !isStateAvailableForAppRequests(state)) {
+        if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app "
@@ -859,14 +867,18 @@
      * @param callingPid Process ID that is requesting this state change
      */
     private void assertCanControlDeviceState(int callingPid) {
-        final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
-        if (topApp == null || topApp.getPid() != callingPid) {
+        if (!isTopApp(callingPid)) {
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to request device state, "
                             + "or the call must come from the top app.");
         }
     }
 
+    private boolean isTopApp(int callingPid) {
+        final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+        return topApp != null && topApp.getPid() == callingPid;
+    }
+
     private boolean isStateAvailableForAppRequests(int state) {
         synchronized (mLock) {
             return mDeviceStatesAvailableForAppRequests.contains(state);
@@ -1185,4 +1197,52 @@
             }
         }
     }
+
+    @GuardedBy("mLock")
+    private boolean shouldCancelOverrideRequestWhenRequesterNotOnTop() {
+        if (mActiveOverride.isEmpty()) {
+            return false;
+        }
+        int identifier = mActiveOverride.get().getRequestedState();
+        DeviceState deviceState = mDeviceStates.get(identifier);
+        return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+    }
+
+    private class OverrideRequestTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() throws RemoteException {
+            synchronized (mLock) {
+                if (!shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+                    return;
+                }
+
+                OverrideRequest request = mActiveOverride.get();
+                if (!isTopApp(request.getPid())) {
+                    mOverrideRequestController.cancelRequest(request);
+                }
+            }
+        }
+    }
+
+    private class OverrideRequestScreenObserver implements
+            ActivityTaskManagerInternal.ScreenObserver {
+
+        @Override
+        public void onAwakeStateChanged(boolean isAwake) {
+            synchronized (mLock) {
+                if (!isAwake && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+                    mOverrideRequestController.cancelRequest(mActiveOverride.get());
+                }
+            }
+        }
+
+        @Override
+        public void onKeyguardStateChanged(boolean isShowing) {
+            synchronized (mLock) {
+                if (isShowing && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+                    mOverrideRequestController.cancelRequest(mActiveOverride.get());
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bb342ff..5087caa 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -177,6 +177,7 @@
     private Layout mCurrentLayout = null;
     private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
     private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+    private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
     private boolean mBootCompleted = false;
     private boolean mInteractive;
 
@@ -373,6 +374,12 @@
         ipw.println("mDeviceStatesOnWhichToWakeUp=" + mDeviceStatesOnWhichToWakeUp);
         ipw.println("mDeviceStatesOnWhichToSleep=" + mDeviceStatesOnWhichToSleep);
         ipw.println("mInteractive=" + mInteractive);
+        ipw.println("mBootCompleted=" + mBootCompleted);
+
+        ipw.println();
+        ipw.println("mDeviceState=" + mDeviceState);
+        ipw.println("mPendingDeviceState=" + mPendingDeviceState);
+        ipw.println("mDeviceStateToBeAppliedAfterBoot=" + mDeviceStateToBeAppliedAfterBoot);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
         ipw.println();
@@ -403,10 +410,6 @@
     }
 
     void setDeviceStateLocked(int state, boolean isOverrideActive) {
-        Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
-                + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
-        mPendingDeviceState = state;
-
         if (!mBootCompleted) {
             // The boot animation might still be in progress, we do not want to switch states now
             // as the boot animation would end up with an incorrect size.
@@ -414,14 +417,19 @@
                 Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
                         + " until boot is completed");
             }
+            mDeviceStateToBeAppliedAfterBoot = state;
             return;
         }
 
+        Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+                + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
         // As part of a state transition, we may need to turn off some displays temporarily so that
         // the transition is smooth. Plus, on some devices, only one internal displays can be
         // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
         // temporarily turned off.
         resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
+        mPendingDeviceState = state;
+        mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
         final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
         final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -468,8 +476,9 @@
     void onBootCompleted() {
         synchronized (mSyncRoot) {
             mBootCompleted = true;
-            if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
-                setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false);
+            if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+                setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
+                        /* isOverrideActive= */ false);
             }
         }
     }
@@ -524,7 +533,8 @@
     @VisibleForTesting
     boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
             boolean isInteractive, boolean isBootCompleted) {
-        return mDeviceStatesOnWhichToSleep.get(pendingState)
+        return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+                && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
                 && !isOverrideActive
                 && isInteractive && isBootCompleted;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index e5357f6..c1780a3 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,9 +16,6 @@
 
 package com.android.server.dreams;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-
-import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
 import android.content.ComponentName;
 import android.content.Context;
@@ -65,7 +62,6 @@
     private final Context mContext;
     private final Handler mHandler;
     private final Listener mListener;
-    private final ActivityTaskManager mActivityTaskManager;
 
     private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
             .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -93,7 +89,6 @@
         mContext = context;
         mHandler = handler;
         mListener = listener;
-        mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mCloseNotificationShadeIntent.putExtra("reason", "dream");
     }
@@ -277,9 +272,6 @@
                     mSentStartBroadcast = false;
                 }
 
-                mActivityTaskManager.removeRootTasksWithActivityTypes(
-                        new int[] {ACTIVITY_TYPE_DREAM});
-
                 mListener.onDreamStopped(dream.mToken);
             }
         } finally {
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 684b5f1..adcda0a 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,15 +16,20 @@
 
 package com.android.server.incident;
 
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IIncidentAuthListener;
 import android.os.IncidentManager;
@@ -32,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionManager;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -55,6 +61,7 @@
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final AppOpsManager mAppOpsManager;
+    private final PermissionManager mPermissionManager;
 
     //
     // All fields below must be protected by mLock
@@ -126,6 +133,7 @@
         mContext = context;
         mPackageManager = context.getPackageManager();
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mPermissionManager = context.getSystemService(PermissionManager.class);
     }
 
     /**
@@ -297,6 +305,35 @@
             return;
         }
 
+        // Only with userdebug/eng build: it could check capture consentless bugreport permission
+        // and approve the report when it's granted.
+        boolean captureConsentlessBugreportOnUserdebugBuildGranted = false;
+        if ((Build.IS_USERDEBUG || Build.IS_ENG)
+                && (flags & IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT) != 0) {
+            AttributionSource attributionSource =
+                    new AttributionSource.Builder(callingUid)
+                            .setPackageName(callingPackage)
+                            .build();
+            captureConsentlessBugreportOnUserdebugBuildGranted =
+                    mPermissionManager.checkPermissionForDataDelivery(
+                            Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+                            attributionSource,
+                            /* message= */ null)
+                            == PERMISSION_GRANTED;
+        }
+        if (captureConsentlessBugreportOnUserdebugBuildGranted) {
+            try {
+                PendingReportRec rec =
+                        new PendingReportRec(
+                                callingPackage, receiverClass, reportId, flags, listener);
+                Log.d(TAG, "approving consentless report: " + rec.getUri());
+                listener.onReportApproved();
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "authorizeReportImpl listener.onReportApproved RemoteException: ", e);
+            }
+        }
+
         // Save the record for when the PermissionController comes back to authorize it.
         PendingReportRec rec = null;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 993b4fd..ff9ce6f 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -234,7 +234,8 @@
     }
 
     private boolean isUsiDevice(int deviceId) {
-        return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+        return processInputDevice(deviceId, false /*defaultValue*/,
+                (device) -> device.getHostUsiVersion() != null);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 35434b7..e21895a 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,14 +136,14 @@
             @ImeVisibilityStateComputer.VisibilityState int state, int reason) {
         switch (state) {
             case STATE_SHOW_IME:
-                ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.get().onProgress(statsToken,
                         ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 // Send to window manager to show IME after IME layout finishes.
                 mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
                 break;
             case STATE_HIDE_IME:
                 if (mService.mCurFocusedWindowClient != null) {
-                    ImeTracker.forLogging().onProgress(statsToken,
+                    ImeTracker.get().onProgress(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                     // IMMS only knows of focused window, not the actual IME target.
                     // e.g. it isn't aware of any window that has both
@@ -154,7 +154,7 @@
                     mWindowManagerInternal.hideIme(windowToken,
                             mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
                 } else {
-                    ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.get().onFailed(statsToken,
                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 }
                 break;
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 10c16b6..db61e954 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -182,10 +182,10 @@
      */
     boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
         if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
-            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
         if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
             mRequestedShowExplicitly = true;
             mShowForced = true;
@@ -206,15 +206,15 @@
         if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mRequestedShowExplicitly || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
-            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
             return false;
         }
         if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
-            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
             return false;
         }
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
         return true;
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ce3abfd..a63b95c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2290,7 +2290,7 @@
             mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
             mCurVirtualDisplayToScreenMatrix = null;
-            ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = null;
 
             mMenuController.hideInputMethodMenuLocked();
@@ -3276,8 +3276,7 @@
                 "InputMethodManagerService#showSoftInput");
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
-                ImeTracker.forLogging().onFailed(
-                        statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -3378,7 +3377,7 @@
             // TODO(b/261565259): to avoid using null, add package name in ClientState
             final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
             final int uid = mCurClient != null ? mCurClient.mUid : -1;
-            statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+            statsToken = ImeTracker.get().onRequestShow(packageName, uid,
                     ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
@@ -3387,19 +3386,19 @@
         }
 
         if (!mSystemReady) {
-            ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
             return false;
         }
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, true);
 
         // Ensure binding the connection when IME is going to show.
         mBindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
-        ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         if (curMethod != null) {
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
             if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
@@ -3410,7 +3409,7 @@
             mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = statsToken;
         }
         return false;
@@ -3426,10 +3425,9 @@
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
                 if (isInputShown()) {
-                    ImeTracker.forLogging().onFailed(
-                            statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 } else {
-                    ImeTracker.forLogging().onCancelled(statsToken,
+                    ImeTracker.get().onCancelled(statsToken,
                             ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 }
                 return false;
@@ -3462,7 +3460,7 @@
             } else {
                 uid = -1;
             }
-            statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+            statsToken = ImeTracker.get().onRequestHide(packageName, uid,
                     ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
@@ -3488,15 +3486,15 @@
             // delivered to the IME process as an IPC.  Hence the inconsistency between
             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
             // the final state.
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
         } else {
-            ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+            ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
         }
         mBindingController.setCurrentMethodNotVisible();
         mVisibilityStateComputer.clearImeShowFlags();
         // Cancel existing statsToken for show IME as we got a hide request.
-        ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         mCurStatsToken = null;
         return shouldHideSoftInput;
     }
@@ -3768,16 +3766,16 @@
             // be made before input is started in it.
             final ClientState cs = mClients.get(client.asBinder());
             if (cs == null) {
-                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
-            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
             if (!isImeClientFocused(mCurFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
         }
-        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
         return true;
     }
 
@@ -4554,8 +4552,7 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
-                ImeTracker.forLogging().onFailed(statsToken,
-                        ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 return;
             }
             final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(windowToken);
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index d252d40..b1c6f8c 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -20,6 +20,7 @@
 
 import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 
 import android.annotation.NonNull;
@@ -586,11 +587,14 @@
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            mArtManagerService.clearAppProfiles(pkg);
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
+        if (useArtService()) {
+            destroyAppProfilesWithArtService(pkg);
+        } else {
+            try {
+                mArtManagerService.clearAppProfiles(pkg);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
     }
 
@@ -629,13 +633,28 @@
     }
 
     private void destroyAppProfilesLeafLIF(AndroidPackage pkg) {
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            mInstaller.destroyAppProfiles(pkg.getPackageName());
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
-        } catch (Installer.InstallerException e) {
-            Slog.w(TAG, String.valueOf(e));
+        if (useArtService()) {
+            destroyAppProfilesWithArtService(pkg);
+        } else {
+            try {
+                mInstaller.destroyAppProfiles(pkg.getPackageName());
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            } catch (Installer.InstallerException e) {
+                Slog.w(TAG, String.valueOf(e));
+            }
+        }
+    }
+
+    private void destroyAppProfilesWithArtService(AndroidPackage pkg) {
+        try (PackageManagerLocal.FilteredSnapshot snapshot =
+                        getPackageManagerLocal().withFilteredSnapshot()) {
+            try {
+                DexOptHelper.getArtManagerLocal().clearAppProfiles(snapshot, pkg.getPackageName());
+            } catch (IllegalArgumentException e) {
+                // Package isn't found, but that should only happen due to race.
+                Slog.w(TAG, e);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c2f0f52..094b1821 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2998,35 +2998,40 @@
                 }
                 ipw.println("Dexopt state:");
                 ipw.increaseIndent();
-                Collection<? extends PackageStateInternal> pkgSettings;
-                if (setting != null) {
-                    pkgSettings = Collections.singletonList(setting);
+                if (DexOptHelper.useArtService()) {
+                    DexOptHelper.dumpDexoptState(ipw, packageName);
                 } else {
-                    pkgSettings = mSettings.getPackages().values();
-                }
-
-                for (PackageStateInternal pkgSetting : pkgSettings) {
-                    final AndroidPackage pkg = pkgSetting.getPkg();
-                    if (pkg == null || pkg.isApex()) {
-                        // Skip APEX which is not dex-optimized
-                        continue;
+                    Collection<? extends PackageStateInternal> pkgSettings;
+                    if (setting != null) {
+                        pkgSettings = Collections.singletonList(setting);
+                    } else {
+                        pkgSettings = mSettings.getPackages().values();
                     }
-                    final String pkgName = pkg.getPackageName();
-                    ipw.println("[" + pkgName + "]");
+
+                    for (PackageStateInternal pkgSetting : pkgSettings) {
+                        final AndroidPackage pkg = pkgSetting.getPkg();
+                        if (pkg == null || pkg.isApex()) {
+                            // Skip APEX which is not dex-optimized
+                            continue;
+                        }
+                        final String pkgName = pkg.getPackageName();
+                        ipw.println("[" + pkgName + "]");
+                        ipw.increaseIndent();
+
+                        // TODO(b/251903639): Call into ART Service.
+                        try {
+                            mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
+                                    mDexManager.getPackageUseInfoOrDefault(pkgName));
+                        } catch (LegacyDexoptDisabledException e) {
+                            throw new RuntimeException(e);
+                        }
+                        ipw.decreaseIndent();
+                    }
+                    ipw.println("BgDexopt state:");
                     ipw.increaseIndent();
-
-                    // TODO(b/251903639): Call into ART Service.
-                    try {
-                        mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
-                                mDexManager.getPackageUseInfoOrDefault(pkgName));
-                    } catch (LegacyDexoptDisabledException e) {
-                        throw new RuntimeException(e);
-                    }
+                    mBackgroundDexOptService.dump(ipw);
                     ipw.decreaseIndent();
                 }
-                ipw.println("BgDexopt state:");
-                ipw.increaseIndent();
-                mBackgroundDexOptService.dump(ipw);
                 ipw.decreaseIndent();
                 break;
             }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 53e23e0..382ae26 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -23,6 +23,7 @@
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAINLINE_UPDATE;
 import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
 import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
 import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
@@ -32,14 +33,12 @@
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
 import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
 
 import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
 
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.Intent;
@@ -52,18 +51,17 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.art.ArtManagerLocal;
 import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.ReasonMapping;
 import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.DexoptParams;
 import com.android.server.art.model.DexoptResult;
@@ -85,10 +83,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
@@ -100,17 +95,9 @@
 
     private final PackageManagerService mPm;
 
-    public boolean isDexOptDialogShown() {
-        synchronized (mLock) {
-            return mDexOptDialogShown;
-        }
-    }
-
-    // TODO: Is this lock really necessary?
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private boolean mDexOptDialogShown;
+    // Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
+    // used, to make it available to the onDexoptDone callback.
+    private volatile long mBootDexoptStartTime;
 
     DexOptHelper(PackageManagerService pm) {
         mPm = pm;
@@ -129,7 +116,7 @@
      * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
      * and {@code numberOfPackagesFailed}.
      */
-    public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates, boolean showDialog,
+    public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates,
             final int compilationReason, boolean bootComplete)
             throws LegacyDexoptDisabledException {
         Installer.checkLegacyDexoptDisabled();
@@ -221,18 +208,6 @@
                         + numberOfPackagesToDexopt + ": " + pkg.getPackageName());
             }
 
-            if (showDialog) {
-                try {
-                    ActivityManager.getService().showBootMessage(
-                            mPm.mContext.getResources().getString(R.string.android_upgrading_apk,
-                                    numberOfPackagesVisited, numberOfPackagesToDexopt), true);
-                } catch (RemoteException e) {
-                }
-                synchronized (mLock) {
-                    mDexOptDialogShown = true;
-                }
-            }
-
             int pkgCompilationReason = compilationReason;
             if (useProfileForDexopt) {
                 // Use background dexopt mode to try and use the profile. Note that this does not
@@ -240,6 +215,7 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
+            // TODO(b/251903639): Do this when ART Service is used, or remove it from here.
             if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
                 mPm.mArtManagerService.compileLayouts(packageState, pkg);
             }
@@ -290,7 +266,7 @@
      * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
      * compiles it if needed.
      */
-    private void checkAndDexOptSystemUi() throws LegacyDexoptDisabledException {
+    private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
         Installer.checkLegacyDexoptDisabled();
         Computer snapshot = mPm.snapshotComputer();
         String sysUiPackageName =
@@ -301,10 +277,6 @@
             return;
         }
 
-        // It could also be after mainline update, but we're not introducing a new reason just for
-        // this special case.
-        int reason = REASON_BOOT_AFTER_OTA;
-
         String defaultCompilerFilter = getCompilerFilterForReason(reason);
         String targetCompilerFilter =
                 SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
@@ -347,49 +319,68 @@
                 compilerFilter, null /* splitName */, dexoptFlags));
     }
 
-    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    public void performPackageDexOptUpgradeIfNeeded() throws LegacyDexoptDisabledException {
+    /**
+     * Called during startup to do any boot time dexopting. This can occasionally be time consuming
+     * (30+ seconds) and the function will block until it is complete.
+     */
+    public void performPackageDexOptUpgradeIfNeeded() {
         PackageManagerServiceUtils.enforceSystemOrRoot(
                 "Only the system can request package update");
 
-        // The default is "true".
-        if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) {
-            // System UI is important to user experience, so we check it after a mainline update or
-            // an OTA. It may need to be re-compiled in these cases.
-            if (hasBcpApexesChanged() || mPm.isDeviceUpgrading()) {
-                checkAndDexOptSystemUi();
-            }
-        }
-
-        // We need to re-extract after an OTA.
-        boolean causeUpgrade = mPm.isDeviceUpgrading();
-
-        // First boot or factory reset.
-        // Note: we also handle devices that are upgrading to N right now as if it is their
-        //       first boot, as they do not have profile data.
-        boolean causeFirstBoot = mPm.isFirstBoot() || mPm.isPreNUpgrade();
-
-        if (!causeUpgrade && !causeFirstBoot) {
+        int reason;
+        if (mPm.isFirstBoot()) {
+            reason = REASON_FIRST_BOOT; // First boot or factory reset.
+        } else if (mPm.isDeviceUpgrading()) {
+            reason = REASON_BOOT_AFTER_OTA;
+        } else if (hasBcpApexesChanged()) {
+            reason = REASON_BOOT_AFTER_MAINLINE_UPDATE;
+        } else {
             return;
         }
 
-        final Computer snapshot = mPm.snapshotComputer();
-        List<PackageStateInternal> pkgSettings =
-                getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
-
         final long startTime = System.nanoTime();
-        final int[] stats = performDexOptUpgrade(pkgSettings, mPm.isPreNUpgrade() /* showDialog */,
-                causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
-                false /* bootComplete */);
 
+        if (useArtService()) {
+            mBootDexoptStartTime = startTime;
+            getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
+                    null /* progressCallbackExecutor */, null /* progressCallback */);
+        } else {
+            try {
+                // System UI is important to user experience, so we check it after a mainline update
+                // or an OTA. It may need to be re-compiled in these cases.
+                checkAndDexOptSystemUi(reason);
+
+                if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
+                    return;
+                }
+
+                final Computer snapshot = mPm.snapshotComputer();
+
+                // TODO(b/251903639): Align this with how ART Service selects packages for boot
+                // compilation.
+                List<PackageStateInternal> pkgSettings =
+                        getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
+
+                final int[] stats =
+                        performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
+                reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
         final int elapsedTimeSeconds =
                 (int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
 
         final Computer newSnapshot = mPm.snapshotComputer();
 
-        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]);
-        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]);
-        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]);
+        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", numDexopted);
+        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", numSkipped);
+        MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", numFailed);
+        // TODO(b/251903639): getOptimizablePackages calls PackageDexOptimizer.canOptimizePackage
+        // which duplicates logic in ART Service (com.android.server.art.Utils.canDexoptPackage).
         MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_total",
                 getOptimizablePackages(newSnapshot).size());
         MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
@@ -421,9 +412,8 @@
 
         @DexOptResult int dexoptStatus;
         if (options.isDexoptOnlySecondaryDex()) {
-            Optional<Integer> artSrvRes = performDexOptWithArtService(options, 0 /* extraFlags */);
-            if (artSrvRes.isPresent()) {
-                dexoptStatus = artSrvRes.get();
+            if (useArtService()) {
+                dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
             } else {
                 try {
                     return mPm.getDexManager().dexoptSecondaryDex(options);
@@ -463,10 +453,8 @@
     // if the package can now be considered up to date for the given filter.
     @DexOptResult
     private int performDexOptInternal(DexoptOptions options) {
-        Optional<Integer> artSrvRes =
-                performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
-        if (artSrvRes.isPresent()) {
-            return artSrvRes.get();
+        if (useArtService()) {
+            return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
         }
 
         AndroidPackage p;
@@ -496,46 +484,26 @@
     }
 
     /**
-     * Performs dexopt on the given package using ART Service.
-     *
-     * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
-     *     necessary to fall back to the legacy code paths.
+     * Performs dexopt on the given package using ART Service. May only be called when ART Service
+     * is enabled, i.e. when {@link useArtService} returns true.
      */
-    private Optional<Integer> performDexOptWithArtService(DexoptOptions options,
+    @DexOptResult
+    private int performDexOptWithArtService(DexoptOptions options,
             /*@DexoptFlags*/ int extraFlags) {
-        ArtManagerLocal artManager = getArtManagerLocal();
-        if (artManager == null) {
-            return Optional.empty();
-        }
-
         try (PackageManagerLocal.FilteredSnapshot snapshot =
                         getPackageManagerLocal().withFilteredSnapshot()) {
             PackageState ops = snapshot.getPackageState(options.getPackageName());
             if (ops == null) {
-                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+                return PackageDexOptimizer.DEX_OPT_FAILED;
             }
             AndroidPackage oap = ops.getAndroidPackage();
             if (oap == null) {
-                return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+                return PackageDexOptimizer.DEX_OPT_FAILED;
             }
-            if (oap.isApex()) {
-                return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
-            }
-
             DexoptParams params = options.convertToDexoptParams(extraFlags);
-            if (params == null) {
-                return Optional.empty();
-            }
-
-            DexoptResult result;
-            try {
-                result = artManager.dexoptPackage(snapshot, options.getPackageName(), params);
-            } catch (UnsupportedOperationException e) {
-                reportArtManagerFallback(options.getPackageName(), e.toString());
-                return Optional.empty();
-            }
-
-            return Optional.of(convertToDexOptResult(result));
+            DexoptResult result =
+                    getArtManagerLocal().dexoptPackage(snapshot, options.getPackageName(), params);
+            return convertToDexOptResult(result);
         }
     }
 
@@ -613,14 +581,12 @@
                 getDefaultCompilerFilter(), null /* splitName */,
                 DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
 
-        // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
-        // the package checks above, but at worst the effect is only a bit less friendly error
-        // below.
-        Optional<Integer> artSrvRes =
-                performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
-        int res;
-        if (artSrvRes.isPresent()) {
-            res = artSrvRes.get();
+        @DexOptResult int res;
+        if (useArtService()) {
+            // performDexOptWithArtService ignores the snapshot and takes its own, so it can race
+            // with the package checks above, but at worst the effect is only a bit less friendly
+            // error below.
+            res = performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
         } else {
             try {
                 res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
@@ -879,6 +845,26 @@
     }
 
     /**
+     * Dumps the dexopt state for the given package, or all packages if it is null.
+     */
+    public static void dumpDexoptState(
+            @NonNull IndentingPrintWriter ipw, @Nullable String packageName) {
+        try (PackageManagerLocal.FilteredSnapshot snapshot =
+                        getPackageManagerLocal().withFilteredSnapshot()) {
+            if (packageName != null) {
+                try {
+                    DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
+                } catch (IllegalArgumentException e) {
+                    // Package isn't found, but that should only happen due to race.
+                    ipw.println(e);
+                }
+            } else {
+                DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
+            }
+        }
+    }
+
+    /**
      * Returns the module names of the APEXes that contribute to bootclasspath.
      */
     private static List<String> getBcpApexes() {
@@ -916,23 +902,6 @@
         return false;
     }
 
-    private @NonNull PackageManagerLocal getPackageManagerLocal() {
-        try {
-            return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
-        } catch (ManagerNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Called whenever we need to fall back from ART Service to the legacy dexopt code.
-     */
-    public static void reportArtManagerFallback(String packageName, String reason) {
-        // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
-        // paths that will always bypass ART Service.
-        Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
-    }
-
     /**
      * Returns true if ART Service should be used for package optimization.
      */
@@ -954,16 +923,39 @@
         }
     }
 
-    private static class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
-        @NonNull private final PackageManagerService mPm;
-
-        DexoptDoneHandler(@NonNull PackageManagerService pm) { mPm = pm; }
-
+    private class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
         /**
-         * Called after every package dexopt operation done by {@link ArtManagerLocal}.
+         * Called after every package dexopt operation done by {@link ArtManagerLocal} (when ART
+         * Service is in use).
          */
         @Override
         public void onDexoptDone(@NonNull DexoptResult result) {
+            switch (result.getReason()) {
+                case ReasonMapping.REASON_FIRST_BOOT:
+                case ReasonMapping.REASON_BOOT_AFTER_OTA:
+                case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+                    int numDexopted = 0;
+                    int numSkipped = 0;
+                    int numFailed = 0;
+                    for (DexoptResult.PackageDexoptResult pkgRes :
+                            result.getPackageDexoptResults()) {
+                        switch (pkgRes.getStatus()) {
+                            case DexoptResult.DEXOPT_PERFORMED:
+                                numDexopted += 1;
+                                break;
+                            case DexoptResult.DEXOPT_SKIPPED:
+                                numSkipped += 1;
+                                break;
+                            case DexoptResult.DEXOPT_FAILED:
+                                numFailed += 1;
+                                break;
+                        }
+                    }
+
+                    reportBootDexopt(mBootDexoptStartTime, numDexopted, numSkipped, numFailed);
+                    break;
+            }
+
             for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
                 CompilerStats.PackageStats stats =
                         mPm.getOrCreateCompilerPackageStats(pkgRes.getPackageName());
@@ -991,22 +983,17 @@
         }
 
         ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
-        // There doesn't appear to be any checks that @NonNull is heeded, so use requireNonNull
-        // below to ensure we don't store away a null that we'll fail on later.
-        artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */,
-                Runnable::run, new DexoptDoneHandler(Objects.requireNonNull(pm)));
+        artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
+                pm.getDexOptHelper().new DexoptDoneHandler());
         LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
 
         artManager.scheduleBackgroundDexoptJob();
     }
 
     /**
-     * Returns {@link ArtManagerLocal} if ART Service should be used for package dexopt.
+     * Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
      */
-    private static @Nullable ArtManagerLocal getArtManagerLocal() {
-        if (!useArtService()) {
-            return null;
-        }
+    public static @NonNull ArtManagerLocal getArtManagerLocal() {
         try {
             return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
         } catch (ManagerNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f0f23cd..a4c9baa 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -158,6 +158,8 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.DexoptParams;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
@@ -388,7 +390,8 @@
         }
 
         if (reconciledPkg.mCollectedSharedLibraryInfos != null
-                || (oldPkgSetting != null && oldPkgSetting.getUsesLibraries() != null)) {
+                || (oldPkgSetting != null
+                && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
             // Reconcile if the new package or the old package uses shared libraries.
             // It is possible that the old package uses shared libraries but the new one doesn't.
             mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
@@ -1080,19 +1083,32 @@
                             "MinInstallableTargetSdk__min_installable_target_sdk",
                             0);
 
+            // Determine if enforcement is in strict mode
+            boolean strictMode = false;
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    "MinInstallableTargetSdk__install_block_strict_mode_enabled",
+                    false)) {
+                if (parsedPackage.getTargetSdkVersion()
+                        < DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                        "MinInstallableTargetSdk__strict_mode_target_sdk",
+                        0)) {
+                    strictMode = true;
+                }
+            }
+
             // Skip enforcement when the bypass flag is set
             boolean bypassLowTargetSdkBlock =
                     ((installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0);
 
             // Skip enforcement for tests that were installed from adb
-            if (!bypassLowTargetSdkBlock
+            if (!strictMode && !bypassLowTargetSdkBlock
                     && ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0)) {
                 bypassLowTargetSdkBlock = true;
             }
 
             // Skip enforcement if the installer package name is not set
             // (e.g. "pm install" from shell)
-            if (!bypassLowTargetSdkBlock) {
+            if (!strictMode && !bypassLowTargetSdkBlock) {
                 if (request.getInstallerPackageName() == null) {
                     bypassLowTargetSdkBlock = true;
                 } else {
@@ -2325,7 +2341,6 @@
     @GuardedBy("mPm.mInstallLock")
     private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        final ArrayList<String> apkPaths = new ArrayList<>();
         for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
@@ -2344,13 +2359,6 @@
                 incrementalStorages.add(storage);
             }
 
-            // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
-            // collect the files to later enable in a background thread.
-            apkPaths.add(pkg.getBaseApkPath());
-            if (pkg.getSplitCodePaths() != null) {
-                Collections.addAll(apkPaths, pkg.getSplitCodePaths());
-            }
-
             // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
             mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
             if (installRequest.isClearCodeCache()) {
@@ -2392,6 +2400,7 @@
                             || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
 
             final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+                    | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                     | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
                     | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
             DexoptOptions dexoptOptions =
@@ -2452,13 +2461,25 @@
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                // TODO(b/251903639): Call into ART Service.
-                try {
-                    mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
-                            null /* instructionSets */, mPm.getOrCreateCompilerPackageStats(pkg),
-                            mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
+                if (useArtService()) {
+                    PackageManagerLocal packageManagerLocal =
+                            LocalManagerRegistry.getManager(PackageManagerLocal.class);
+                    try (PackageManagerLocal.FilteredSnapshot snapshot =
+                                    packageManagerLocal.withFilteredSnapshot()) {
+                        DexoptParams params =
+                                dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+                        DexOptHelper.getArtManagerLocal().dexoptPackage(
+                                snapshot, packageName, params);
+                    }
+                } else {
+                    try {
+                        mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+                                null /* instructionSets */,
+                                mPm.getOrCreateCompilerPackageStats(pkg),
+                                mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
+                    } catch (LegacyDexoptDisabledException e) {
+                        throw new RuntimeException(e);
+                    }
                 }
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
@@ -2479,20 +2500,6 @@
         }
         PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
                 incrementalStorages);
-
-        mInjector.getBackgroundHandler().post(() -> {
-            for (String path : apkPaths) {
-                if (!VerityUtils.hasFsverity(path)) {
-                    try {
-                        VerityUtils.setUpFsverity(path);
-                    } catch (IOException e) {
-                        // There's nothing we can do if the setup failed. Since fs-verity is
-                        // optional, just ignore the error for now.
-                        Slog.e(TAG, "Failed to fully enable fs-verity to " + path);
-                    }
-                }
-            }
-        });
     }
 
     Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 490b2a9..767c0a7 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -168,13 +168,7 @@
             Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
                     + DexOptHelper.packagesToString(others));
             for (PackageStateInternal pkg : others) {
-                // TODO(b/251903639): Call into ART Service.
-                try {
-                    mPackageManagerService.deleteOatArtifactsOfPackage(
-                            snapshot, pkg.getPackageName());
-                } catch (LegacyDexoptDisabledException e) {
-                    throw new RuntimeException(e);
-                }
+                mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
             }
         }
         long spaceAvailableNow = getAvailableSpace();
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 99fff72..4e75210 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -47,7 +47,6 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -708,12 +707,7 @@
     @Override
     @Deprecated
     public final long deleteOatArtifactsOfPackage(String packageName) {
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
-        }
+        return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f95093..9a15e11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -196,6 +196,7 @@
 import com.android.server.Watchdog;
 import com.android.server.apphibernation.AppHibernationManagerInternal;
 import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.model.DeleteResult;
 import com.android.server.compat.CompatChange;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.Installer.InstallerException;
@@ -552,6 +553,7 @@
     private static final int REQUIRED_VERIFIERS_MAX_COUNT = 2;
 
     // Compilation reasons.
+    // TODO(b/260124949): Clean this up with the legacy dexopt code.
     public static final int REASON_FIRST_BOOT = 0;
     public static final int REASON_BOOT_AFTER_OTA = 1;
     public static final int REASON_POST_BOOT = 2;
@@ -565,7 +567,8 @@
     public static final int REASON_AB_OTA = 10;
     public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
     public static final int REASON_CMDLINE = 12;
-    public static final int REASON_SHARED = 13;
+    public static final int REASON_BOOT_AFTER_MAINLINE_UPDATE = 13;
+    public static final int REASON_SHARED = 14;
 
     public static final int REASON_LAST = REASON_SHARED;
 
@@ -588,7 +591,6 @@
     private final int mDefParseFlags;
     private final String[] mSeparateProcesses;
     private final boolean mIsUpgrade;
-    private final boolean mIsPreNUpgrade;
     private final boolean mIsPreNMR1Upgrade;
     private final boolean mIsPreQUpgrade;
 
@@ -1734,7 +1736,6 @@
         mInstantAppResolverConnection = testParams.instantAppResolverConnection;
         mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
         mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
-        mIsPreNUpgrade = testParams.isPreNupgrade;
         mIsPreQUpgrade = testParams.isPreQupgrade;
         mIsUpgrade = testParams.isUpgrade;
         mMetrics = testParams.Metrics;
@@ -2069,10 +2070,6 @@
             mPromoteSystemApps =
                     mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
 
-            // When upgrading from pre-N, we need to handle package extraction like first boot,
-            // as there is no profiling data available.
-            mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;
-
             mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
             mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;
 
@@ -2954,13 +2951,12 @@
                 }
                 if (doTrim) {
                     if (!isFirstBoot()) {
-                        if (mDexOptHelper.isDexOptDialogShown()) {
-                            try {
-                                ActivityManager.getService().showBootMessage(
-                                        mContext.getResources().getString(
-                                                R.string.android_upgrading_fstrim), true);
-                            } catch (RemoteException e) {
-                            }
+                        try {
+                            ActivityManager.getService().showBootMessage(
+                                    mContext.getResources().getString(
+                                            R.string.android_upgrading_fstrim),
+                                    true);
+                        } catch (RemoteException e) {
                         }
                     }
                     sm.runMaintenance();
@@ -2974,12 +2970,7 @@
     }
 
     public void updatePackagesIfNeeded() {
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
-        }
+        mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
     }
 
     private void notifyPackageUseInternal(String packageName, int reason) {
@@ -2997,6 +2988,10 @@
         return mDexManager;
     }
 
+    /*package*/ DexOptHelper getDexOptHelper() {
+        return mDexOptHelper;
+    }
+
     /*package*/ DynamicCodeLogger getDynamicCodeLogger() {
         return mDynamicCodeLogger;
     }
@@ -5435,9 +5430,11 @@
                             String loadingPkgDexCodeIsa = InstructionSets.getDexCodeInstructionSet(
                                     VMRuntime.getInstructionSet(loadingPkgAbi));
                             if (!loaderIsa.equals(loadingPkgDexCodeIsa)) {
-                                // TODO(b/251903639): Make this crash to surface this problem
-                                // better.
-                                Slog.w(PackageManagerService.TAG,
+                                // TODO(b/251903639): We make this a wtf to surface any situations
+                                // where this argument doesn't correspond to our expectations. Later
+                                // it should be turned into an IllegalArgumentException, when we can
+                                // assume it's the caller that's wrong rather than us.
+                                Log.wtf(TAG,
                                         "Invalid loaderIsa in notifyDexLoad call from "
                                                 + loadingPackageName + ", uid " + callingUid
                                                 + ": expected " + loadingPkgDexCodeIsa + ", got "
@@ -7097,17 +7094,39 @@
         return AndroidPackageUtils.canHaveOatDir(packageState, packageState.getPkg());
     }
 
-    long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName)
-            throws LegacyDexoptDisabledException {
+    long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
         PackageManagerServiceUtils.enforceSystemOrRootOrShell(
                 "Only the system or shell can delete oat artifacts");
 
-        PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-        if (packageState == null || packageState.getPkg() == null) {
-            return -1; // error code of deleteOptimizedFiles
+        if (DexOptHelper.useArtService()) {
+            // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+            try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+                            PackageManagerServiceUtils.getPackageManagerLocal()
+                                    .withFilteredSnapshot()) {
+                try {
+                    DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+                            filteredSnapshot, packageName);
+                    return res.getFreedBytes();
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, e.toString());
+                    return -1;
+                } catch (IllegalStateException e) {
+                    Slog.wtfStack(TAG, e.toString());
+                    return -1;
+                }
+            }
+        } else {
+            PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
+            if (packageState == null || packageState.getPkg() == null) {
+                return -1; // error code of deleteOptimizedFiles
+            }
+            try {
+                return mDexManager.deleteOptimizedFiles(
+                        ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
-        return mDexManager.deleteOptimizedFiles(
-                ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
     }
 
     List<String> getMimeGroupInternal(@NonNull Computer snapshot, String packageName,
@@ -7476,10 +7495,6 @@
         return mPlatformPackage;
     }
 
-    boolean isPreNUpgrade() {
-        return mIsPreNUpgrade;
-    }
-
     boolean isPreNMR1Upgrade() {
         return mIsPreNMR1Upgrade;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 7c1f054..e2ddba5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -41,6 +41,7 @@
         "ab-ota",
         "inactive",
         "cmdline",
+        "boot-after-mainline-update",
         // "shared" must be the last entry
         "shared"
     };
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 08ff51d..e5cfa67 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -63,7 +63,6 @@
     public InstantAppResolverConnection instantAppResolverConnection;
     public ComponentName instantAppResolverSettingsComponent;
     public boolean isPreNmr1Upgrade;
-    public boolean isPreNupgrade;
     public boolean isPreQupgrade;
     public boolean isUpgrade;
     public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 0de1a4e..ad8e35d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -24,6 +24,7 @@
 import static android.system.OsConstants.O_RDWR;
 
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -91,6 +92,7 @@
 import com.android.internal.util.HexDump;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.Watchdog;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -201,6 +203,17 @@
     private static final boolean FORCE_PACKAGE_PARSED_CACHE_ENABLED = false;
 
     /**
+     * Returns the registered PackageManagerLocal instance, or else throws an unchecked error.
+     */
+    public static @NonNull PackageManagerLocal getPackageManagerLocal() {
+        try {
+            return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
+        } catch (ManagerNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
      * Package is considered active, if:
      * 1) It was active in foreground.
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 53fdfaa..2a1172c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1219,7 +1219,7 @@
 
     @NonNull
     @Override
-    public List<SharedLibrary> getUsesLibraries() {
+    public List<SharedLibrary> getSharedLibraryDependencies() {
         return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
     }
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index e5aaddb..10673c6 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -422,15 +422,18 @@
             if (instructionSets == null) {
                 throw new IllegalStateException("instructionSet == null");
             }
-            String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-            for (String codePath : allCodePaths) {
-                for (String dexCodeInstructionSet : dexCodeInstructionSets) {
-                    // TODO(b/251903639): Call into ART Service.
-                    try {
-                        mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
-                    } catch (LegacyDexoptDisabledException e) {
-                        throw new RuntimeException(e);
-                    } catch (Installer.InstallerException ignored) {
+            // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
+            // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
+            if (!DexOptHelper.useArtService()) {
+                String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+                for (String codePath : allCodePaths) {
+                    for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+                        try {
+                            mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
+                        } catch (LegacyDexoptDisabledException e) {
+                            throw new RuntimeException(e);
+                        } catch (Installer.InstallerException ignored) {
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 53a5648..328fc8b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -35,6 +35,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
@@ -2924,7 +2925,13 @@
 
                 final Intent broadcast = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)
                         .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId));
+                // Setting the MOST_RECENT policy allows us to discard older broadcasts
+                // still waiting to be delivered.
+                final Bundle options = BroadcastOptions.makeBasic()
+                        .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                        .toBundle();
+                mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId),
+                        null /* receiverPermission */, options);
             }
         });
     }
@@ -3718,14 +3725,12 @@
         }
 
         if (userVersion < 6) {
-            final boolean splitSystemUser = UserManager.isSplitSystemUser();
             synchronized (mUsersLock) {
                 for (int i = 0; i < mUsers.size(); i++) {
                     UserData userData = mUsers.valueAt(i);
-                    // In non-split mode, only user 0 can have restricted profiles
-                    if (!splitSystemUser && userData.info.isRestricted()
-                            && (userData.info.restrictedProfileParentId
-                                    == UserInfo.NO_PROFILE_GROUP_ID)) {
+                    // Only system user can have restricted profiles
+                    if (userData.info.isRestricted() && (userData.info.restrictedProfileParentId
+                            == UserInfo.NO_PROFILE_GROUP_ID)) {
                         userData.info.restrictedProfileParentId = UserHandle.USER_SYSTEM;
                         userIdsToWrite.add(userData.info.id);
                     }
@@ -6496,7 +6501,6 @@
         pw.println("  All guests ephemeral: " + Resources.getSystem().getBoolean(
                 com.android.internal.R.bool.config_guestUserEphemeral));
         pw.println("  Force ephemeral users: " + mForceEphemeralUsers);
-        pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
         final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
         pw.println("  Is headless-system mode: " + isHeadlessSystemUserMode);
         if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index b5b6347..d8b6cd5 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -50,11 +50,15 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
+import com.android.server.pm.PackageManagerLocal;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.PackageManagerServiceCompilerMapping;
+import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
@@ -210,20 +214,15 @@
             Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
         }
 
-        // TODO(b/251903639): Call into ART Service.
-        try {
-            if (bootImageProfile) {
-                snapshotBootImageProfile(callback);
-            } else {
-                snapshotAppProfile(packageName, codePath, callback);
-            }
-        } catch (LegacyDexoptDisabledException e) {
-            throw new RuntimeException(e);
+        if (bootImageProfile) {
+            snapshotBootImageProfile(callback);
+        } else {
+            snapshotAppProfile(packageName, codePath, callback);
         }
     }
 
-    private void snapshotAppProfile(String packageName, String codePath,
-            ISnapshotRuntimeProfileCallback callback) throws LegacyDexoptDisabledException {
+    private void snapshotAppProfile(
+            String packageName, String codePath, ISnapshotRuntimeProfileCallback callback) {
         PackageInfo info = null;
         try {
             // Note that we use the default user 0 to retrieve the package info.
@@ -260,17 +259,45 @@
         }
 
         // All good, create the profile snapshot.
-        int appId = UserHandle.getAppId(info.applicationInfo.uid);
-        if (appId < 0) {
-            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-            Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
-            return;
-        }
+        if (DexOptHelper.useArtService()) {
+            ParcelFileDescriptor fd;
 
-        createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
-                appId, callback);
-        // Destroy the snapshot, we no longer need it.
-        destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+            try (PackageManagerLocal.FilteredSnapshot snapshot =
+                            PackageManagerServiceUtils.getPackageManagerLocal()
+                                    .withFilteredSnapshot()) {
+                fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
+                        snapshot, packageName, splitName);
+            } catch (IllegalArgumentException e) {
+                // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
+                // we've checked them above this can only happen due to race, i.e. the package got
+                // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
+                // for the split.
+                // TODO(mast): Reuse the same snapshot to avoid this race.
+                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
+                return;
+            } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+                return;
+            }
+
+            postSuccess(packageName, fd, callback);
+        } else {
+            int appId = UserHandle.getAppId(info.applicationInfo.uid);
+            if (appId < 0) {
+                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+                Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
+                return;
+            }
+
+            try {
+                createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
+                        appId, callback);
+                // Destroy the snapshot, we no longer need it.
+                destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 
     private void createProfileSnapshot(String packageName, String profileName, String classpath,
@@ -340,23 +367,43 @@
         }
     }
 
-    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback)
-            throws LegacyDexoptDisabledException {
-        // Combine the profiles for boot classpath and system server classpath.
-        // This avoids having yet another type of profiles and simplifies the processing.
-        String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
-                Os.getenv("SYSTEMSERVERCLASSPATH"));
+    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+        if (DexOptHelper.useArtService()) {
+            ParcelFileDescriptor fd;
 
-        final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
-        if (standaloneSystemServerJars != null) {
-            classpath = String.join(":", classpath, standaloneSystemServerJars);
+            try (PackageManagerLocal.FilteredSnapshot snapshot =
+                            PackageManagerServiceUtils.getPackageManagerLocal()
+                                    .withFilteredSnapshot()) {
+                fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
+            } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+                postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
+                        ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+                return;
+            }
+
+            postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
+        } else {
+            // Combine the profiles for boot classpath and system server classpath.
+            // This avoids having yet another type of profiles and simplifies the processing.
+            String classpath = String.join(
+                    ":", Os.getenv("BOOTCLASSPATH"), Os.getenv("SYSTEMSERVERCLASSPATH"));
+
+            final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+            if (standaloneSystemServerJars != null) {
+                classpath = String.join(":", classpath, standaloneSystemServerJars);
+            }
+
+            try {
+                // Create the snapshot.
+                createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME,
+                        classpath,
+                        /*appId*/ -1, callback);
+                // Destroy the snapshot, we no longer need it.
+                destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
+            } catch (LegacyDexoptDisabledException e) {
+                throw new RuntimeException(e);
+            }
         }
-
-        // Create the snapshot.
-        createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
-                /*appId*/ -1, callback);
-        // Destroy the snapshot, we no longer need it.
-        destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
     }
 
     /**
@@ -620,6 +667,7 @@
     private static final int TRON_COMPILATION_REASON_CMDLINE = 22;
     private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
     private static final int TRON_COMPILATION_REASON_VDEX = 24;
+    private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -634,6 +682,8 @@
             case "error" : return TRON_COMPILATION_REASON_ERROR;
             case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
             case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+            case "boot-after-mainline-update":
+                return TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE;
             case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
             case "install" : return TRON_COMPILATION_REASON_INSTALL;
             case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index d3fba7c..310c0e8 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -20,18 +20,20 @@
 
 import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.util.Log;
 
 import com.android.server.art.ReasonMapping;
 import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.DexoptParams;
-import com.android.server.pm.DexOptHelper;
 import com.android.server.pm.PackageManagerService;
 
 /**
  * Options used for dexopt invocations.
  */
 public final class DexoptOptions {
+    private static final String TAG = "DexoptOptions";
+
     // When set, the profiles will be checked for updates before calling dexopt. If
     // the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt
     // will be skipped.
@@ -87,8 +89,9 @@
     // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags.
     private final int mFlags;
 
-    // When not null, dexopt will optimize only the split identified by this name.
-    // It only applies for primary apk and it's always null if mOnlySecondaryDex is true.
+    // When not null, dexopt will optimize only the split identified by this APK file name (not
+    // split name). It only applies for primary apk and it's always null if mOnlySecondaryDex is
+    // true.
     private final String mSplitName;
 
     // The reason for invoking dexopt (see PackageManagerService.REASON_* constants).
@@ -201,19 +204,68 @@
     }
 
     /**
+     * Returns the ART Service reason for the given PackageManagerService reason. Throws unchecked
+     * exceptions for reasons that aren't supported.
+     */
+    public static @NonNull String convertToArtServiceDexoptReason(int pmDexoptReason) {
+        switch (pmDexoptReason) {
+            case PackageManagerService.REASON_FIRST_BOOT:
+                return ReasonMapping.REASON_FIRST_BOOT;
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+                return ReasonMapping.REASON_BOOT_AFTER_OTA;
+            case PackageManagerService.REASON_INSTALL:
+                return ReasonMapping.REASON_INSTALL;
+            case PackageManagerService.REASON_INSTALL_FAST:
+                return ReasonMapping.REASON_INSTALL_FAST;
+            case PackageManagerService.REASON_INSTALL_BULK:
+                return ReasonMapping.REASON_INSTALL_BULK;
+            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
+                return ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
+            case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
+                return ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
+            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+                return ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+            case PackageManagerService.REASON_BACKGROUND_DEXOPT:
+                return ReasonMapping.REASON_BG_DEXOPT;
+            case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
+                return ReasonMapping.REASON_INACTIVE;
+            case PackageManagerService.REASON_CMDLINE:
+                return ReasonMapping.REASON_CMDLINE;
+            case PackageManagerService.REASON_POST_BOOT:
+            case PackageManagerService.REASON_SHARED:
+            case PackageManagerService.REASON_AB_OTA:
+                // REASON_POST_BOOT isn't supported - that dexopt stage is getting removed.
+                // REASON_SHARED shouldn't go to ART Service - it's only used at lower levels
+                // in PackageDexOptimizer.
+                // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
+                // either.
+                throw new UnsupportedOperationException(
+                        "ART Service unsupported compilation reason " + pmDexoptReason);
+            default:
+                throw new IllegalArgumentException("Invalid compilation reason " + pmDexoptReason);
+        }
+    }
+
+    /**
      * Returns an {@link DexoptParams} instance corresponding to this object, for use with
      * {@link com.android.server.art.ArtManagerLocal}.
      *
      * @param extraFlags extra {@link ArtFlags#DexoptFlags} to set in the returned
      *     {@code DexoptParams} beyond those converted from this object
-     * @return null if the settings cannot be accurately represented, and hence the old
-     *     PackageManager/installd code paths need to be used.
+     * @throws UnsupportedOperationException if the settings cannot be accurately represented.
      */
-    public @Nullable DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
+    public @NonNull DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
         if (mSplitName != null) {
-            DexOptHelper.reportArtManagerFallback(
-                    mPackageName, "Request to optimize only split " + mSplitName);
-            return null;
+            // ART Service supports dexopting a single split - see ArtFlags.FLAG_FOR_SINGLE_SPLIT.
+            // However using it here requires searching through the splits to find the one matching
+            // the APK file name in mSplitName, and we don't have the AndroidPackage available for
+            // that.
+            //
+            // Hence we throw here instead, under the assumption that no code paths that dexopt
+            // splits need this conversion (e.g. shell commands with the --split argument are
+            // handled by ART Service directly).
+            throw new UnsupportedOperationException(
+                    "Request to optimize only split " + mSplitName + " for " + mPackageName);
         }
 
         /*@DexoptFlags*/ int flags = extraFlags;
@@ -236,11 +288,11 @@
             flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
         }
         if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
-            // ART Service cannot be instructed to ignore a DM file if present, so not setting this
-            // flag is not supported.
-            DexOptHelper.reportArtManagerFallback(
-                    mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
-            return null;
+            // ART Service cannot be instructed to ignore a DM file if present.
+            Log.w(TAG,
+                    "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set in request to optimise "
+                            + mPackageName
+                            + " - ART Service will unconditionally use a DM file if present.");
         }
 
         /*@PriorityClassApi*/ int priority;
@@ -269,60 +321,7 @@
         // -  DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to
         //    be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc).
 
-        String reason;
-        switch (mCompilationReason) {
-            case PackageManagerService.REASON_FIRST_BOOT:
-                reason = ReasonMapping.REASON_FIRST_BOOT;
-                break;
-            case PackageManagerService.REASON_BOOT_AFTER_OTA:
-                reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
-                break;
-            case PackageManagerService.REASON_POST_BOOT:
-                // This reason will go away with the legacy dexopt code.
-                DexOptHelper.reportArtManagerFallback(
-                        mPackageName, "Unsupported compilation reason REASON_POST_BOOT");
-                return null;
-            case PackageManagerService.REASON_INSTALL:
-                reason = ReasonMapping.REASON_INSTALL;
-                break;
-            case PackageManagerService.REASON_INSTALL_FAST:
-                reason = ReasonMapping.REASON_INSTALL_FAST;
-                break;
-            case PackageManagerService.REASON_INSTALL_BULK:
-                reason = ReasonMapping.REASON_INSTALL_BULK;
-                break;
-            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
-                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
-                break;
-            case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
-                reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
-                break;
-            case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
-                reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
-                break;
-            case PackageManagerService.REASON_BACKGROUND_DEXOPT:
-                reason = ReasonMapping.REASON_BG_DEXOPT;
-                break;
-            case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
-                reason = ReasonMapping.REASON_INACTIVE;
-                break;
-            case PackageManagerService.REASON_CMDLINE:
-                reason = ReasonMapping.REASON_CMDLINE;
-                break;
-            case PackageManagerService.REASON_SHARED:
-            case PackageManagerService.REASON_AB_OTA:
-                // REASON_SHARED shouldn't go into this code path - it's only used at lower levels
-                // in PackageDexOptimizer.
-                // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
-                // either.
-                throw new UnsupportedOperationException(
-                        "ART Service unsupported compilation reason " + mCompilationReason);
-            default:
-                throw new IllegalArgumentException(
-                        "Invalid compilation reason " + mCompilationReason);
-        }
-
-        return new DexoptParams.Builder(reason, flags)
+        return new DexoptParams.Builder(convertToArtServiceDexoptReason(mCompilationReason), flags)
                 .setCompilerFilter(mCompilerFilter)
                 .setPriorityClass(priority)
                 .build();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a12c9d0..106b149 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -158,10 +158,11 @@
     PackageUserState getStateForUser(@NonNull UserHandle user);
 
     /**
-     * @see R.styleable#AndroidManifestUsesLibrary
+     * List of shared libraries that this package declares a dependency on. This includes all
+     * types of libraries, system or app provided and Java or native.
      */
     @NonNull
-    List<SharedLibrary> getUsesLibraries();
+    List<SharedLibrary> getSharedLibraryDependencies();
 
     /** Whether this represents an APEX module. This is different from an APK inside an APEX. */
     boolean isApex();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index bc6dab4..91a25db3 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -190,7 +190,7 @@
         mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
         mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
         mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
-        mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
+        mUsesLibraries = Collections.unmodifiableList(pkgState.getSharedLibraryDependencies());
         mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
         setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
         setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
@@ -693,7 +693,7 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<SharedLibrary> getUsesLibraries() {
+    public @NonNull List<SharedLibrary> getSharedLibraryDependencies() {
         return mUsesLibraries;
     }
 
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 91bb677..7f733ef 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -27,6 +27,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Environment;
+import android.os.PowerManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -80,7 +81,8 @@
  * provided.
  */
 public final class DeviceStateProviderImpl implements DeviceStateProvider,
-        InputManagerInternal.LidSwitchCallback, SensorEventListener {
+        InputManagerInternal.LidSwitchCallback, SensorEventListener,
+        PowerManager.OnThermalStatusChangedListener {
     private static final String TAG = "DeviceStateProviderImpl";
     private static final boolean DEBUG = false;
 
@@ -97,6 +99,10 @@
     private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
     private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
     private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
+    private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+            "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+    private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL =
+            "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL";
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -152,6 +158,13 @@
                                     break;
                                 case FLAG_EMULATED_ONLY:
                                     flags |= DeviceState.FLAG_EMULATED_ONLY;
+                                    break;
+                                case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+                                    flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+                                    break;
+                                case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL:
+                                    flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL;
+                                    break;
                                 default:
                                     Slog.w(TAG, "Parsed unknown flag with name: "
                                             + configFlagString);
@@ -194,6 +207,8 @@
     private Boolean mIsLidOpen;
     @GuardedBy("mLock")
     private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
 
     private DeviceStateProviderImpl(@NonNull Context context,
             @NonNull List<DeviceState> deviceStates,
@@ -208,6 +223,16 @@
         mOrderedStates = orderedStates;
 
         setStateConditions(deviceStates, stateConditions);
+
+        // If any of the device states are thermal sensitive, i.e. it should be disabled when the
+        // device is overheating, then we will update the list of supported states when thermal
+        // status changes.
+        if (hasThermalSensitiveState(deviceStates)) {
+            PowerManager powerManager = context.getSystemService(PowerManager.class);
+            if (powerManager != null) {
+                powerManager.addThermalStatusListener(this);
+            }
+        }
     }
 
     private void setStateConditions(@NonNull List<DeviceState> deviceStates,
@@ -347,16 +372,25 @@
 
     /** Notifies the listener that the set of supported device states has changed. */
     private void notifySupportedStatesChanged() {
-        DeviceState[] supportedStates;
+        List<DeviceState> supportedStates = new ArrayList<>();
+        Listener listener;
         synchronized (mLock) {
             if (mListener == null) {
                 return;
             }
-
-            supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
+            listener = mListener;
+            for (DeviceState deviceState : mOrderedStates) {
+                if (isThermalStatusCriticalOrAbove(mThermalStatus)
+                        && deviceState.hasFlag(
+                                DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+                    continue;
+                }
+                supportedStates.add(deviceState);
+            }
         }
 
-        mListener.onSupportedDeviceStatesChanged(supportedStates);
+        listener.onSupportedDeviceStatesChanged(
+                supportedStates.toArray(new DeviceState[supportedStates.size()]));
     }
 
     /** Computes the current device state and notifies the listener of a change, if needed. */
@@ -639,4 +673,43 @@
             return new FileInputStream(mFile);
         }
     }
+
+    @Override
+    public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
+        int previousThermalStatus;
+        synchronized (mLock) {
+            previousThermalStatus = mThermalStatus;
+            mThermalStatus = thermalStatus;
+        }
+
+        boolean isThermalStatusCriticalOrAbove = isThermalStatusCriticalOrAbove(thermalStatus);
+        boolean isPreviousThermalStatusCriticalOrAbove =
+                isThermalStatusCriticalOrAbove(previousThermalStatus);
+        if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
+            Slog.i(TAG, "Updating supported device states due to thermal status change."
+                    + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
+            notifySupportedStatesChanged();
+        }
+    }
+
+    private static boolean isThermalStatusCriticalOrAbove(
+            @PowerManager.ThermalStatus int thermalStatus) {
+        switch (thermalStatus) {
+            case PowerManager.THERMAL_STATUS_CRITICAL:
+            case PowerManager.THERMAL_STATUS_EMERGENCY:
+            case PowerManager.THERMAL_STATUS_SHUTDOWN:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
+        for (DeviceState state : deviceStates) {
+            if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 983b7f4..fa81540 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -282,8 +282,9 @@
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
-                if (Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserAdmin()) {
+                if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, mContext.getUserId()) != 0
+                        && isCurrentUserAdmin()) {
                     mItems.add(new BugReportAction());
                 }
             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
@@ -537,7 +538,7 @@
 
     private boolean isCurrentUserAdmin() {
         UserInfo currentUser = getCurrentUser();
-        return currentUser == null || currentUser.isAdmin();
+        return currentUser != null && currentUser.isAdmin();
     }
 
     private void addUsersToMenu(ArrayList<Action> items) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b7a801a..302f4c4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -717,7 +717,7 @@
                     handleRingerChordGesture();
                     break;
                 case MSG_SCREENSHOT_CHORD:
-                    handleScreenShot(msg.arg1, msg.arg2);
+                    handleScreenShot(msg.arg1);
                     break;
             }
         }
@@ -1518,9 +1518,9 @@
                 || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
     }
 
-    private void interceptScreenshotChord(int type, int source, long pressDelay) {
+    private void interceptScreenshotChord(int source, long pressDelay) {
         mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
                 pressDelay);
     }
 
@@ -1590,9 +1590,8 @@
         }
     };
 
-    private void handleScreenShot(@WindowManager.ScreenshotType int type,
-            @WindowManager.ScreenshotSource int source) {
-        mDefaultDisplayPolicy.takeScreenshot(type, source);
+    private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
+        mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
     }
 
     @Override
@@ -2228,7 +2227,7 @@
                         @Override
                         void execute() {
                             mPowerKeyHandled = true;
-                            interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
+                            interceptScreenshotChord(
                                     SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
                         }
                         @Override
@@ -2956,8 +2955,7 @@
                 break;
             case KeyEvent.KEYCODE_S:
                 if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
-                    interceptScreenshotChord(
-                            TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                     return key_consumed;
                 }
                 break;
@@ -3402,8 +3400,7 @@
                 break;
             case KeyEvent.KEYCODE_SYSRQ:
                 if (down && repeatCount == 0) {
-                    interceptScreenshotChord(
-                            TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                 }
                 return true;
         }
@@ -4344,7 +4341,7 @@
             // from windowmanager. Currently, we need to ensure the setInputWindows completes,
             // which would force the focus event to be queued before the current key event.
             // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
-            Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+            Log.i(TAG, "Attempting to move non-focused display " + displayId + " to top "
                     + "because a key is targeting it");
             mWindowManagerFuncs.moveDisplayToTopIfAllowed(displayId);
         }
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 79e35c2..54f3476 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -20,8 +20,10 @@
 import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
 
 import android.content.Context;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
+import android.util.IntArray;
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -32,6 +34,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IntPair;
 
 import java.util.Arrays;
@@ -43,12 +46,16 @@
  * Stores stats about CPU wakeups and tries to attribute them to subsystems and uids.
  */
 public class CpuWakeupStats {
+    private static final String TAG = "CpuWakeupStats";
+
     private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
     @VisibleForTesting
     static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
     @VisibleForTesting
     static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+    private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000;  // 2 minutes.
 
+    private final Handler mHandler;
     private final IrqDeviceMap mIrqDeviceMap;
     private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
 
@@ -58,8 +65,58 @@
     final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
             new TimeSparseArray<>();
 
-    public CpuWakeupStats(Context context, int mapRes) {
+    public CpuWakeupStats(Context context, int mapRes, Handler handler) {
         mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+        mHandler = handler;
+    }
+
+    private static int subsystemToStatsReason(int subsystem) {
+        switch (subsystem) {
+            case CPU_WAKEUP_SUBSYSTEM_ALARM:
+                return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM;
+        }
+        return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
+    }
+
+    private synchronized void logWakeupToStatsLog(Wakeup wakeupToLog) {
+        if (ArrayUtils.isEmpty(wakeupToLog.mDevices)) {
+            FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+                    FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN,
+                    FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN,
+                    null,
+                    wakeupToLog.mElapsedMillis);
+            return;
+        }
+
+        final SparseArray<SparseBooleanArray> wakeupAttribution = mWakeupAttribution.get(
+                wakeupToLog.mElapsedMillis);
+        if (wakeupAttribution == null) {
+            // This is not expected but can theoretically happen in extreme situations, e.g. if we
+            // remove the wakeup before the handler gets to process this message.
+            Slog.wtf(TAG, "Unexpected null attribution found for " + wakeupToLog);
+            return;
+        }
+        for (int i = 0; i < wakeupAttribution.size(); i++) {
+            final int subsystem = wakeupAttribution.keyAt(i);
+            final SparseBooleanArray uidMap = wakeupAttribution.valueAt(i);
+            final int[] uids;
+            if (uidMap == null || uidMap.size() == 0) {
+                uids = new int[0];
+            } else {
+                final IntArray tmp = new IntArray(uidMap.size());
+                for (int j = 0; j < uidMap.size(); j++) {
+                    if (uidMap.valueAt(j)) {
+                        tmp.add(uidMap.keyAt(j));
+                    }
+                }
+                uids = tmp.toArray();
+            }
+            FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+                    FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
+                    subsystemToStatsReason(subsystem),
+                    uids,
+                    wakeupToLog.mElapsedMillis);
+        }
     }
 
     /** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
@@ -83,6 +140,7 @@
         for (int i = lastIdx; i >= 0; i--) {
             mWakeupAttribution.removeAt(i);
         }
+        mHandler.postDelayed(() -> logWakeupToStatsLog(parsedWakeup), WAKEUP_WRITE_DELAY_MS);
     }
 
     /** Notes a waking activity that could have potentially woken up the CPU. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 075bac1..b146767 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -87,7 +87,6 @@
 import android.os.IInterface;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -2169,12 +2168,14 @@
     @Override
     public ParcelFileDescriptor getWallpaper(String callingPkg, IWallpaperManagerCallback cb,
             final int which, Bundle outParams, int wallpaperUserId) {
-        return getWallpaperWithFeature(callingPkg, null, cb, which, outParams, wallpaperUserId);
+        return getWallpaperWithFeature(callingPkg, null, cb, which, outParams,
+                wallpaperUserId, /* getCropped= */ true);
     }
 
     @Override
     public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
-            IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId) {
+            IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
+            boolean getCropped) {
         final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
         if (!hasPrivilege) {
             mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
@@ -2209,10 +2210,14 @@
                 if (cb != null) {
                     wallpaper.callbacks.register(cb);
                 }
-                if (!wallpaper.cropFile.exists()) {
+
+                File fileToReturn = getCropped ? wallpaper.cropFile : wallpaper.wallpaperFile;
+
+                if (!fileToReturn.exists()) {
                     return null;
                 }
-                return ParcelFileDescriptor.open(wallpaper.cropFile, MODE_READ_ONLY);
+
+                return ParcelFileDescriptor.open(fileToReturn, MODE_READ_ONLY);
             } catch (FileNotFoundException e) {
                 /* Shouldn't happen as we check to see if the file exists */
                 Slog.w(TAG, "Error getting wallpaper", e);
@@ -2250,6 +2255,25 @@
     }
 
     @Override
+    public ParcelFileDescriptor getWallpaperInfoFile(int userId) {
+        synchronized (mLock) {
+            try {
+                File file = new File(getWallpaperDir(userId), WALLPAPER_INFO);
+
+                if (!file.exists()) {
+                    return null;
+                }
+
+                return ParcelFileDescriptor.open(file, MODE_READ_ONLY);
+            } catch (FileNotFoundException e) {
+                /* Shouldn't happen as we check to see if the file exists */
+                Slog.w(TAG, "Error getting wallpaper info file", e);
+            }
+            return null;
+        }
+    }
+
+    @Override
     public int getWallpaperIdForUser(int which, int userId) {
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getWallpaperIdForUser", null);
@@ -3201,10 +3225,6 @@
 
     @Override
     public boolean isWallpaperBackupEligible(int which, int userId) {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("Only the system may call isWallpaperBackupEligible");
-        }
-
         WallpaperData wallpaper = (which == FLAG_LOCK)
                 ? mLockWallpaperMap.get(userId)
                 : mWallpaperMap.get(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90d25ee..4f87b1a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5257,9 +5257,9 @@
             transferStartingWindowFromHiddenAboveTokenIfNeeded();
         }
 
-        // If in a transition, defer commits for activities that are going invisible
-        if (!visible && inTransition()) {
-            if (mTransitionController.inPlayingTransition(this)
+        // Defer committing visibility until transition starts.
+        if (inTransition()) {
+            if (!visible && mTransitionController.inPlayingTransition(this)
                     && mTransitionController.isCollecting(this)) {
                 mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
             }
@@ -5509,6 +5509,17 @@
         }
     }
 
+    /** Updates draw state and shows drawn windows. */
+    void commitFinishDrawing(SurfaceControl.Transaction t) {
+        boolean committed = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            committed |= mChildren.get(i).commitFinishDrawing(t);
+        }
+        if (committed) {
+            requestUpdateWallpaperIfNeeded();
+        }
+    }
+
     /**
      * Check if visibility of this {@link ActivityRecord} should be updated as part of an app
      * transition.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index bd4f1a6..2bd9052 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -24,6 +24,7 @@
 import android.app.BackgroundStartPrivileges;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
+import android.app.ITaskStackListener;
 import android.app.ProfilerInfo;
 import android.content.ComponentName;
 import android.content.IIntentSender;
@@ -740,4 +741,10 @@
      */
     public abstract void restartTaskActivityProcessIfVisible(
             int taskId, @NonNull String packageName);
+
+    /** Sets the task stack listener that gets callbacks when a task stack changes. */
+    public abstract void registerTaskStackListener(ITaskStackListener listener);
+
+    /** Unregister a task stack listener so that it stops receiving callbacks. */;
+    public abstract void unregisterTaskStackListener(ITaskStackListener listener);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6fe77b7..a927ed3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6916,5 +6916,17 @@
                 activity.restartProcessIfVisible();
             }
         }
+
+        /** Sets the task stack listener that gets callbacks when a task stack changes. */
+        @Override
+        public void registerTaskStackListener(ITaskStackListener listener) {
+            ActivityTaskManagerService.this.registerTaskStackListener(listener);
+        }
+
+        /** Unregister a task stack listener so that it stops receiving callbacks. */
+        @Override
+        public void unregisterTaskStackListener(ITaskStackListener listener) {
+            ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 63d6509..0bd59a8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6841,12 +6841,12 @@
         public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {
             try {
-                ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.get().onProgress(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
                 mRemoteInsetsController.showInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver showInsets", e);
-                ImeTracker.forLogging().onFailed(statsToken,
+                ImeTracker.get().onFailed(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
             }
         }
@@ -6855,12 +6855,12 @@
         public void hideInsets(@InsetsType int types, boolean fromIme,
                 @Nullable ImeTracker.Token statsToken) {
             try {
-                ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.get().onProgress(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
                 mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to deliver hideInsets", e);
-                ImeTracker.forLogging().onFailed(statsToken,
+                ImeTracker.get().onFailed(statsToken,
                         ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a68d7af..cfcf459 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -129,6 +129,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.view.AppearanceRegion;
 import com.android.internal.widget.PointerLocationView;
@@ -2451,8 +2452,9 @@
      */
     public void takeScreenshot(int screenshotType, int source) {
         if (mScreenshotHelper != null) {
-            mScreenshotHelper.takeScreenshot(screenshotType,
-                    source, mHandler, null /* completionConsumer */);
+            ScreenshotRequest request =
+                    new ScreenshotRequest.Builder(screenshotType, source).build();
+            mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 60e2e95..85938e3 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,8 +182,7 @@
         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
         // There was still a stats token, so that request presumably failed.
-        ImeTracker.forLogging().onFailed(
-                mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
         mImeRequesterStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
@@ -198,12 +197,12 @@
         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
                 ? mImeRequester : mImeRequester.getWindow().getName());
         mShowImeRunner = () -> {
-            ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
+            ImeTracker.get().onProgress(mImeRequesterStatsToken,
                     ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
             ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
             // Target should still be the same.
             if (isReadyToShowIme()) {
-                ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
+                ImeTracker.get().onProgress(mImeRequesterStatsToken,
                         ImeTracker.PHASE_WM_SHOW_IME_READY);
                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
 
@@ -220,7 +219,7 @@
                                     ? mImeRequester.getWindow().getName() : ""));
                 }
             } else {
-                ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
+                ImeTracker.get().onFailed(mImeRequesterStatsToken,
                         ImeTracker.PHASE_WM_SHOW_IME_READY);
             }
             // Clear token here so we don't report an error in abortShowImePostLayout().
@@ -259,8 +258,7 @@
         mImeRequester = null;
         mIsImeLayoutDrawn = false;
         mShowImeRunner = null;
-        ImeTracker.forLogging().onCancelled(
-                mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
         mImeRequesterStatsToken = null;
     }
 
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 874f942..1df534f 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -707,8 +707,7 @@
 
         InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
             super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */,
-                    null /* loggingListener */, null /* jankContext */);
+                    false /* disable */, 0 /* floatingImeBottomInsets */, null);
             mFinishCallback = finishCallback;
             mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index cd23959..8b1fb51 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -70,6 +70,7 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -211,6 +212,8 @@
     private IContainerFreezer mContainerFreezer = null;
     private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
 
+    final TransitionController.Logger mLogger = new TransitionController.Logger();
+
     Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
@@ -219,6 +222,8 @@
         mSyncEngine = syncEngine;
         mToken = new Token(this);
 
+        mLogger.mCreateWallTimeMs = System.currentTimeMillis();
+        mLogger.mCreateTimeNs = SystemClock.uptimeNanos();
         controller.mTransitionTracer.logState(this);
     }
 
@@ -380,6 +385,8 @@
         mState = STATE_COLLECTING;
         mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
 
+        mLogger.mSyncId = mSyncId;
+        mLogger.mCollectTimeNs = SystemClock.uptimeNanos();
         mController.mTransitionTracer.logState(this);
     }
 
@@ -399,6 +406,7 @@
                 mSyncId);
         applyReady();
 
+        mLogger.mStartTimeNs = SystemClock.uptimeNanos();
         mController.mTransitionTracer.logState(this);
 
         mController.updateAnimatingState(mTmpTransaction);
@@ -608,6 +616,7 @@
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Set transition ready=%b %d", ready, mSyncId);
         mSyncEngine.setReady(mSyncId, ready);
+        if (ready) mLogger.mReadyTimeNs = SystemClock.uptimeNanos();
     }
 
     /**
@@ -759,6 +768,8 @@
             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
                     System.identityHashCode(this));
         }
+        mLogger.mFinishTimeNs = SystemClock.uptimeNanos();
+        mController.mLoggerHandler.post(mLogger::logOnFinish);
         // Close the transactions now. They were originally copied to Shell in case we needed to
         // apply them due to a remote failure. Since we don't need to apply them anymore, free them
         // immediately.
@@ -845,7 +856,7 @@
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
             if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
             if (inputSinkTransaction == null) {
-                inputSinkTransaction = new SurfaceControl.Transaction();
+                inputSinkTransaction = ar.mWmService.mTransactionFactory.get();
             }
             ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
         }
@@ -961,6 +972,12 @@
         // time being, we don't have full cross-display transitions so it isn't a problem.
         final DisplayContent dc = mTargetDisplays.get(0);
 
+        // Commit the visibility of visible activities before calculateTransitionInfo(), so the
+        // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
+        // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
+        // needs to be updated for STATE_ABORT.
+        commitVisibleActivities(transaction);
+
         if (mState == STATE_ABORT) {
             mController.abort(this);
             dc.getPendingTransaction().merge(transaction);
@@ -1082,6 +1099,8 @@
             try {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "Calling onTransitionReady: %s", info);
+                mLogger.mSendTimeNs = SystemClock.uptimeNanos();
+                mLogger.mInfo = info;
                 mController.getTransitionPlayer().onTransitionReady(
                         mToken, info, transaction, mFinishTransaction);
                 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1097,6 +1116,7 @@
             // No player registered, so just finish/apply immediately
             cleanUpOnFailure();
         }
+        mController.mLoggerHandler.post(mLogger::logOnSend);
         mOverrideOptions = null;
 
         reportStartReasonsToLogger();
@@ -1132,6 +1152,19 @@
         }
     }
 
+    /** The transition is ready to play. Make the start transaction show the surfaces. */
+    private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+            if (ar == null || !ar.isVisibleRequested()) {
+                continue;
+            }
+            ar.commitVisibility(true /* visible */, false /* performLayout */,
+                    true /* fromTransition */);
+            ar.commitFinishDrawing(transaction);
+        }
+    }
+
     /** @see RecentsAnimationController#attachNavigationBarToApp */
     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
         if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 73cd251..5e116ba 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
 import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.WindowConfiguration;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
@@ -38,6 +39,7 @@
 import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -46,11 +48,13 @@
 import android.window.RemoteTransition;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
@@ -120,6 +124,8 @@
 
     private boolean mAnimatingState = false;
 
+    final Handler mLoggerHandler = FgThread.getHandler();
+
     TransitionController(ActivityTaskManagerService atm,
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
@@ -462,9 +468,11 @@
                 info = new ActivityManager.RunningTaskInfo();
                 startTask.fillTaskInfo(info);
             }
-            mTransitionPlayer.requestStartTransition(transition.getToken(),
-                    new TransitionRequestInfo(transition.mType, info, remoteTransition,
-                            displayChange));
+            final TransitionRequestInfo request = new TransitionRequestInfo(
+                    transition.mType, info, remoteTransition, displayChange);
+            transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
+            transition.mLogger.mRequest = request;
+            mTransitionPlayer.requestStartTransition(transition.getToken(), request);
             transition.setRemoteTransition(remoteTransition);
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
@@ -847,6 +855,66 @@
         }
     }
 
+    /**
+     * Data-class to store recorded events/info for a transition. This allows us to defer the
+     * actual logging until the system isn't busy. This also records some common metrics to see
+     * delays at-a-glance.
+     *
+     * Beside `mCreateWallTimeMs`, all times are elapsed times and will all be reported relative
+     * to when the transition was created.
+     */
+    static class Logger {
+        long mCreateWallTimeMs;
+        long mCreateTimeNs;
+        long mRequestTimeNs;
+        long mCollectTimeNs;
+        long mStartTimeNs;
+        long mReadyTimeNs;
+        long mSendTimeNs;
+        long mFinishTimeNs;
+        TransitionRequestInfo mRequest;
+        WindowContainerTransaction mStartWCT;
+        int mSyncId;
+        TransitionInfo mInfo;
+
+        private String buildOnSendLog() {
+            StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
+                    .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            if (mRequest != null) {
+                sb.append(" via request=").append(mRequest);
+            }
+            return sb.toString();
+        }
+
+        void logOnSend() {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    startWCT=%s", mStartWCT);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s", mInfo);
+        }
+
+        private static String toMsString(long nanos) {
+            return ((double) Math.round((double) nanos / 1000) / 1000) + "ms";
+        }
+
+        private String buildOnFinishLog() {
+            StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
+                    .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+            sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
+            if (mRequestTimeNs != 0) {
+                sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
+            }
+            sb.append(" started=").append(toMsString(mStartTimeNs - mCreateTimeNs));
+            sb.append(" ready=").append(toMsString(mReadyTimeNs - mCreateTimeNs));
+            sb.append(" sent=").append(toMsString(mSendTimeNs - mCreateTimeNs));
+            sb.append(" finished=").append(toMsString(mFinishTimeNs - mCreateTimeNs));
+            return sb.toString();
+        }
+
+        void logOnFinish() {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnFinishLog());
+        }
+    }
+
     static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
         private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 18ad43c..8767096 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8087,14 +8087,14 @@
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
                 }
                 if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
-                    ImeTracker.forLogging().onProgress(statsToken,
+                    ImeTracker.get().onProgress(statsToken,
                             ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                     ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
                             dc.getImeTarget(IME_TARGET_CONTROL));
                     dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
                             true /* fromIme */, statsToken);
                 } else {
-                    ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.get().onFailed(statsToken,
                             ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                 }
                 if (dc != null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6a1adb4..5c68b12 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -318,6 +318,7 @@
                     transition = mTransitionController.createTransition(type);
                 }
                 transition.start();
+                transition.mLogger.mStartWCT = wct;
                 applyTransaction(wct, -1 /*syncId*/, transition, caller);
                 if (needsSetReady) {
                     transition.setAllReady();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7a0070b..223352e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3993,12 +3993,12 @@
     public void showInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
         try {
-            ImeTracker.forLogging().onProgress(statsToken,
+            ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
             mClient.showInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to deliver showInsets", e);
-            ImeTracker.forLogging().onFailed(statsToken,
+            ImeTracker.get().onFailed(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
         }
     }
@@ -4007,12 +4007,12 @@
     public void hideInsets(@InsetsType int types, boolean fromIme,
             @Nullable ImeTracker.Token statsToken) {
         try {
-            ImeTracker.forLogging().onProgress(statsToken,
+            ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
             mClient.hideInsets(types, fromIme, statsToken);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to deliver hideInsets", e);
-            ImeTracker.forLogging().onFailed(statsToken,
+            ImeTracker.get().onFailed(statsToken,
                     ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
         }
     }
@@ -4616,6 +4616,19 @@
         }
     }
 
+    /** Makes the surface of drawn window (COMMIT_DRAW_PENDING) to be visible. */
+    boolean commitFinishDrawing(SurfaceControl.Transaction t) {
+        boolean committed = mWinAnimator.commitFinishDrawingLocked();
+        if (committed) {
+            // Ensure that the visibility of buffer layer is set.
+            mWinAnimator.prepareSurfaceLocked(t);
+        }
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            committed |= mChildren.get(i).commitFinishDrawing(t);
+        }
+        return committed;
+    }
+
     // This must be called while inside a transaction.
     boolean performShowLocked() {
         if (!showToCurrentUser()) {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index acfa491..668f8ed6 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,7 +35,7 @@
 import java.util.ArrayList;
 
 /**
- * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * Central session for a single {@link CredentialManager#createCredential} request.
  * This class listens to the responses from providers, and the UX app, and updates the
  * provider(s) state maintained in {@link ProviderCreateSession}.
  */
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
new file mode 100644
index 0000000..b7c5fc2
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.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.server.credentials;
+
+import android.credentials.CredentialDescription;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Contains information on what CredentialProvider has what provisioned Credential. */
+public class CredentialDescriptionRegistry {
+
+    private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
+    private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
+
+    static {
+        sCredentialDescriptionSessionPerUser = new SparseArray<>();
+    }
+
+    // TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
+    /** Get and/or create a {@link  CredentialDescription} for the given user id. */
+    public static CredentialDescriptionRegistry forUser(int userId) {
+        CredentialDescriptionRegistry session =
+                sCredentialDescriptionSessionPerUser.get(userId, null);
+
+        if (session == null) {
+            session = new CredentialDescriptionRegistry();
+            sCredentialDescriptionSessionPerUser.put(userId, session);
+        }
+        return session;
+    }
+
+    private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
+
+    private CredentialDescriptionRegistry() {
+        this.mCredentialDescriptions = new HashMap<>();
+    }
+
+    /** Handle the given {@link RegisterCredentialDescriptionRequest} by creating
+     * the appropriate package name mapping. */
+    public void executeRegisterRequest(RegisterCredentialDescriptionRequest request,
+            String callingPackageName,
+            IRegisterCredentialDescriptionCallback callback) {
+
+        if (!mCredentialDescriptions.containsKey(callingPackageName)
+                && mCredentialDescriptions.size() <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS) {
+            mCredentialDescriptions.put(callingPackageName, new HashSet<>());
+        }
+
+        mCredentialDescriptions.get(callingPackageName)
+                .addAll(request.getCredentialDescriptions());
+
+        try {
+            callback.onResponse();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating
+     * the appropriate package name mapping. */
+    public void executeUnregisterRequest(
+            UnregisterCredentialDescriptionRequest request,
+            String callingPackageName,
+            IUnregisterCredentialDescriptionCallback callback) {
+
+        if (mCredentialDescriptions.containsKey(callingPackageName)) {
+            mCredentialDescriptions.get(callingPackageName)
+                    .remove(request.getCredentialDescription());
+        }
+
+        try {
+            callback.onResponse();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Returns package names of CredentialProviders that can satisfy a given
+     * {@link CredentialDescription}. */
+    public Set<String> filterCredentials(String flatRequestString) {
+
+        Set<String> result = new HashSet<>();
+
+        for (String componentName: mCredentialDescriptions.keySet()) {
+            Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
+            for (CredentialDescription containedDescription: currentSet) {
+                if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
+                    result.add(componentName);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    void evictProviderWithPackageName(String packageName) {
+        if (mCredentialDescriptions.containsKey(packageName)) {
+            mCredentialDescriptions.remove(packageName);
+        }
+    }
+
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index f76cf49..620b81b 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,14 +36,19 @@
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
 import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
 import android.credentials.ISetEnabledProvidersCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
 import android.credentials.ListEnabledProvidersResponse;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
 import android.credentials.ui.IntentFactory;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.BeginGetCredentialRequest;
@@ -59,9 +64,13 @@
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Entry point service for credential management.
@@ -75,6 +84,8 @@
                 CredentialManagerService, CredentialManagerServiceImpl> {
 
     private static final String TAG = "CredManSysService";
+    private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+            "enable_credential_description_api";
 
     private final Context mContext;
 
@@ -164,6 +175,7 @@
         if (services == null) {
             return;
         }
+
         CredentialManagerServiceImpl serviceToBeRemoved = null;
         for (CredentialManagerServiceImpl service : services) {
             if (service != null) {
@@ -180,10 +192,14 @@
         }
         if (serviceToBeRemoved != null) {
             removeServiceFromCache(serviceToBeRemoved, userId);
+            CredentialDescriptionRegistry.forUser(userId)
+                    .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
         }
         // TODO("Iterate over system services and remove if needed")
     }
 
+
+
     @GuardedBy("mLock")
     private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
             int resolvedUserId) {
@@ -223,6 +239,53 @@
         concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
         return concatenatedServices;
     }
+    public static boolean isCredentialDescriptionApiEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+    }
+
+    @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
+    // to be guarded by 'service.mLock', which is the same as mLock.
+    private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
+            RequestSession session,
+            List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
+        List<ProviderSession> providerSessions = new ArrayList<>();
+        // Invoke all services of a user to initiate a provider session
+        runForUser((service) -> {
+            if (activeCredentialContainers.contains(service.getComponentName())) {
+                ProviderSession providerSession = service
+                        .initiateProviderSessionForRequestLocked(session, requestOptions);
+                if (providerSession != null) {
+                    providerSessions.add(providerSession);
+                }
+            }
+        });
+        return providerSessions;
+    }
+
+    @NonNull
+    private Set<String> getMatchingProviders(GetCredentialRequest request) {
+        // Session for active/provisioned credential descriptions;
+        CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
+                .forUser(UserHandle.getCallingUserId());
+
+        // All requested credential descriptions based on the given request.
+        Set<String> requestedCredentialDescriptions =
+                request.getGetCredentialOptions().stream().map(
+                        getCredentialOption -> getCredentialOption
+                                        .getCredentialRetrievalData()
+                                        .getString(RegisterCredentialDescriptionRequest
+                                                .FLATTENED_REQUEST_STRING_KEY))
+                        .collect(Collectors.toSet());
+
+        // All requested credential descriptions based on the given request.
+        return requestedCredentialDescriptions.stream()
+                .map(registry::filterCredentials)
+                .flatMap(
+                        (Function<Set<String>, Stream<String>>)
+                                Collection::stream)
+                .collect(Collectors.toSet());
+    }
 
     @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
     // to be guarded by 'service.mLock', which is the same as mLock.
@@ -282,11 +345,11 @@
 
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
-                    initiateProviderSessions(
-                            session,
-                            request.getGetCredentialOptions().stream()
-                                    .map(GetCredentialOption::getType)
-                                    .collect(Collectors.toList()));
+                        initiateProviderSessions(
+                                session,
+                                request.getGetCredentialOptions().stream()
+                                        .map(GetCredentialOption::getType)
+                                        .collect(Collectors.toList()));
 
             if (providerSessions.isEmpty()) {
                 try {
@@ -316,7 +379,7 @@
                 ICreateCredentialCallback callback,
                 String callingPackage) {
             Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
-            // TODO : Implement cancellation
+
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
 
             // New request session, scoped for this request only.
@@ -478,5 +541,77 @@
                     });
             return cancelTransport;
         }
+
+        @Override
+        public ICancellationSignal registerCredentialDescription(
+                RegisterCredentialDescriptionRequest request,
+                IRegisterCredentialDescriptionCallback callback, String callingPackage) {
+            Log.i(TAG, "registerCredentialDescription");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+
+            List<CredentialProviderInfo> services =
+                    CredentialProviderInfo.getAvailableServices(mContext,
+                            UserHandle.getCallingUserId());
+
+            List<String> providers = services.stream()
+                    .map(credentialProviderInfo
+                            -> credentialProviderInfo.getServiceInfo().packageName).toList();
+            if (!providers.contains(callingPackage)) {
+                try {
+                    callback.onError("UNKNOWN",
+                            "Not an existing provider.");
+                } catch (RemoteException e) {
+                    Log.i(
+                            TAG,
+                            "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+                                    + "callback: "
+                                    + e.getMessage());
+                }
+            }
+
+            CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+                    .forUser(UserHandle.getCallingUserId());
+
+            session.executeRegisterRequest(request, callingPackage, callback);
+
+            return cancelTransport;
+        }
+
+        @Override
+        public ICancellationSignal unRegisterCredentialDescription(
+                UnregisterCredentialDescriptionRequest request,
+                IUnregisterCredentialDescriptionCallback callback, String callingPackage) {
+            Log.i(TAG, "registerCredentialDescription");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            List<CredentialProviderInfo> services =
+                    CredentialProviderInfo.getAvailableServices(mContext,
+                            UserHandle.getCallingUserId());
+
+            List<String> providers = services.stream()
+                    .map(credentialProviderInfo
+                            -> credentialProviderInfo.getServiceInfo().packageName).toList();
+
+            if (!providers.contains(callingPackage)) {
+                try {
+                    callback.onError("UNKNOWN",
+                            "Not an existing provider.");
+                } catch (RemoteException e) {
+                    Log.i(
+                            TAG,
+                            "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+                                    + "callback: "
+                                    + e.getMessage());
+                }
+            }
+
+            CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+                    .forUser(UserHandle.getCallingUserId());
+
+            session.executeUnregisterRequest(request, callingPackage, callback);
+
+            return cancelTransport;
+        }
     }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5c5442d..3725756 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2736,6 +2736,10 @@
         mSystemServiceManager.startService(PermissionPolicyService.class);
         t.traceEnd();
 
+        t.traceBegin("ArtManagerLocal");
+        DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
+        t.traceEnd();
+
         t.traceBegin("MakePackageManagerServiceReady");
         mPackageManagerService.systemReady();
         t.traceEnd();
@@ -2770,10 +2774,6 @@
         mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
-        t.traceBegin("ArtManagerLocal");
-        DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
-        t.traceEnd();
-
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
             t.traceBegin("UwbService");
             mSystemServiceManager.startServiceFromJar(UWB_SERVICE_CLASS, UWB_APEX_SERVICE_JAR_PATH);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index ae8dd41..0ca4dfc 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -1162,9 +1162,9 @@
 
         private final int mUserId;
 
-        // Conversation package name + shortcut ID -> Number of active notifications
+        // Conversation package name + shortcut ID -> Keys of active notifications
         @GuardedBy("this")
-        private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
+        private final Map<Pair<String, String>, Set<String>> mActiveNotifKeys = new ArrayMap<>();
 
         private NotificationListener(int userId) {
             mUserId = userId;
@@ -1178,8 +1178,10 @@
             String shortcutId = sbn.getNotification().getShortcutId();
             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
                 synchronized (this) {
-                    mActiveNotifCounts.merge(
-                            Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
+                    Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+                            Pair.create(sbn.getPackageName(), shortcutId),
+                            (unusedKey) -> new HashSet<>());
+                    notificationKeys.add(sbn.getKey());
                 }
             });
 
@@ -1218,12 +1220,12 @@
                 Pair<String, String> conversationKey =
                         Pair.create(sbn.getPackageName(), shortcutId);
                 synchronized (this) {
-                    int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
-                    if (count <= 0) {
-                        mActiveNotifCounts.remove(conversationKey);
+                    Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+                            conversationKey, (unusedKey) -> new HashSet<>());
+                    notificationKeys.remove(sbn.getKey());
+                    if (notificationKeys.isEmpty()) {
+                        mActiveNotifKeys.remove(conversationKey);
                         cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
-                    } else {
-                        mActiveNotifCounts.put(conversationKey, count);
                     }
                 }
             });
@@ -1289,7 +1291,7 @@
         }
 
         synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
-            return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
+            return mActiveNotifKeys.containsKey(Pair.create(packageName, shortcutId));
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 5f4ff1a..45fe795 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1191,31 +1191,6 @@
     }
 
     /**
-     * Verify that we detect and ANR a wedged process when delivering a
-     * broadcast with more than one priority tranche.
-     */
-    @Test
-    public void testWedged_Registered_Prioritized() throws Exception {
-        // Legacy stack doesn't detect these ANRs; likely an oversight
-        Assume.assumeTrue(mImpl == Impl.MODERN);
-
-        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
-        final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN,
-                ProcessBehavior.WEDGE);
-        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE,
-                ProcessBehavior.NORMAL);
-
-        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
-                List.of(makeRegisteredReceiver(receiverGreenApp, 10),
-                        makeRegisteredReceiver(receiverBlueApp, 5))));
-
-        waitForIdle();
-        verify(mAms).appNotResponding(eq(receiverGreenApp), any());
-        verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
-    }
-
-    /**
      * Verify that we handle registered receivers in a process that always
      * responds with {@link DeadObjectException}, recovering to restart the
      * process and deliver their next broadcast.
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 3f16a98..3deb903 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -1048,8 +1048,7 @@
             Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
             consumer.accept(Uri.parse("a/b.png"));
             return null;
-        }).when(mMockScreenshotHelper).provideScreenshot(
-                any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
+        }).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
         mGameServiceProviderInstance.start();
         startTask(taskId, GAME_A_MAIN_ACTIVITY);
         mFakeGameService.requestCreateGameSession(taskId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 7e1a42b..ddb6f23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -30,6 +30,7 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -855,6 +856,9 @@
                 case WORK_TYPE_FGS:
                     workTypeString = "fgs";
                     break;
+                case WORK_TYPE_UI:
+                    workTypeString = "ui";
+                    break;
                 case WORK_TYPE_EJ:
                     workTypeString = "ej";
                     break;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index a924207..19b5ad6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -29,6 +29,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
@@ -59,6 +60,7 @@
 import android.content.pm.ServiceInfo;
 import android.graphics.Color;
 import android.hardware.display.DisplayManager;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -100,6 +102,8 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 
@@ -158,6 +162,9 @@
         sContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT,
                 PackageManager.PERMISSION_GRANTED);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.READ_WALLPAPER_INTERNAL,
+                PackageManager.PERMISSION_GRANTED);
         doNothing().when(sContext).sendBroadcastAsUser(any(), any());
 
         //Wallpaper components
@@ -494,6 +501,50 @@
                 colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
     }
 
+    @Test
+    public void getWallpaperWithFeature_getCropped_returnsCropFile() throws Exception {
+        File cropSystemWallpaperFile =
+                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER_CROP);
+        cropSystemWallpaperFile.createNewFile();
+        try (FileOutputStream outputStream = new FileOutputStream(cropSystemWallpaperFile)) {
+            outputStream.write("Crop system wallpaper".getBytes());
+        }
+
+        ParcelFileDescriptor pfd =
+                mService.getWallpaperWithFeature(
+                        sContext.getPackageName(),
+                        sContext.getAttributionTag(),
+                        /* cb= */ null,
+                        FLAG_SYSTEM,
+                        /* outParams= */ null,
+                        USER_SYSTEM,
+                        /* getCropped= */ true);
+
+        assertPfdAndFileContentsEqual(pfd, cropSystemWallpaperFile);
+    }
+
+    @Test
+    public void getWallpaperWithFeature_notGetCropped_returnsOriginalFile() throws Exception {
+        File originalSystemWallpaperFile =
+                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER);
+        originalSystemWallpaperFile.createNewFile();
+        try (FileOutputStream outputStream = new FileOutputStream(originalSystemWallpaperFile)) {
+            outputStream.write("Original system wallpaper".getBytes());
+        }
+
+        ParcelFileDescriptor pfd =
+                mService.getWallpaperWithFeature(
+                        sContext.getPackageName(),
+                        sContext.getAttributionTag(),
+                        /* cb= */ null,
+                        FLAG_SYSTEM,
+                        /* outParams= */ null,
+                        USER_SYSTEM,
+                        /* getCropped= */ false);
+
+        assertPfdAndFileContentsEqual(pfd, originalSystemWallpaperFile);
+    }
+
     // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
     // non-current user must not bind to wallpaper service.
     private void verifyNoConnectionBeforeLastUser(int lastUserId) {
@@ -529,4 +580,22 @@
                     data.mHeight >= DISPLAY_SIZE_DIMENSION);
         });
     }
+
+    /**
+     * Asserts that the contents of the given {@link ParcelFileDescriptor} and {@link File} contain
+     * exactly the same bytes.
+     *
+     * Both the PFD and File contents will be loaded to memory. The PFD will be closed at the end.
+     */
+    private static void assertPfdAndFileContentsEqual(ParcelFileDescriptor pfd, File file)
+            throws IOException {
+        try (ParcelFileDescriptor.AutoCloseInputStream pfdInputStream =
+                     new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+             FileInputStream fileInputStream = new FileInputStream(file)
+        ) {
+            String pfdContents = new String(pfdInputStream.readAllBytes());
+            String fileContents = new String(fileInputStream.readAllBytes());
+            assertEquals(pfdContents, fileContents);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 13d93cb..d9461aa 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
@@ -305,9 +304,7 @@
         mSystemActionPerformer.performSystemAction(
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
-                eq(TAKE_SCREENSHOT_FULLSCREEN),
-                eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
-                any(Handler.class), any());
+                eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
     }
 
     // PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 00d4a6d..6a4435f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -75,6 +76,7 @@
 import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchEvent;
 import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
 import android.net.MacAddress;
 import android.net.Uri;
 import android.os.Binder;
@@ -223,6 +225,8 @@
     @Mock
     private IVirtualDeviceActivityListener mActivityListener;
     @Mock
+    private IVirtualDeviceSoundEffectListener mSoundEffectListener;
+    @Mock
     private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     @Mock
     private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -1572,6 +1576,13 @@
                 intent.filterEquals(blockedAppIntent)), any(), any());
     }
 
+    @Test
+    public void playSoundEffect_callsSoundEffectListener() throws Exception {
+        mVdm.playSoundEffect(mDeviceImpl.getDeviceId(), AudioManager.FX_KEY_CLICK);
+
+        verify(mSoundEffectListener).onPlaySoundEffect(AudioManager.FX_KEY_CLICK);
+    }
+
     private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
         VirtualDeviceParams params = new VirtualDeviceParams.Builder()
                 .setBlockedActivities(getBlockedActivities())
@@ -1585,7 +1596,8 @@
                 mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
                 mInputController, mSensorController, mCameraAccessController,
                 /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
-                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+                mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
+                mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index a45144e..82f6493 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -67,6 +67,9 @@
             new DeviceState(0, "DEFAULT", 0 /* flags */);
     private static final DeviceState OTHER_DEVICE_STATE =
             new DeviceState(1, "OTHER", 0 /* flags */);
+    private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+            new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
+                    DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
     // A device state that is not reported as being supported for the default test provider.
     private static final DeviceState UNSUPPORTED_DEVICE_STATE =
             new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
@@ -77,6 +80,7 @@
     private TestDeviceStateProvider mProvider;
     private DeviceStateManagerService mService;
     private TestSystemPropertySetter mSysPropSetter;
+    private WindowProcessController mWindowProcessController;
 
     @Before
     public void setup() {
@@ -88,10 +92,10 @@
 
         // Necessary to allow us to check for top app process id in tests
         mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
-        WindowProcessController windowProcessController = mock(WindowProcessController.class);
+        mWindowProcessController = mock(WindowProcessController.class);
         when(mService.mActivityTaskManagerInternal.getTopApp())
-                .thenReturn(windowProcessController);
-        when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+                .thenReturn(mWindowProcessController);
+        when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
 
         flushHandler(); // Flush the handler to ensure the initial values are committed.
     }
@@ -201,7 +205,7 @@
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE);
+                OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
         flushHandler();
@@ -234,10 +238,10 @@
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE);
+                OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE});
+                OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP});
         flushHandler();
 
         // The current committed and requests states do not change because the current state remains
@@ -248,7 +252,7 @@
                 DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
         assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
         assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE);
+                OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
 
         // The callback wasn't notified about a change in supported states as the states have not
         // changed.
@@ -261,7 +265,8 @@
         assertNotNull(info);
         assertArrayEquals(info.supportedStates,
                 new int[] { DEFAULT_DEVICE_STATE.getIdentifier(),
-                        OTHER_DEVICE_STATE.getIdentifier() });
+                        OTHER_DEVICE_STATE.getIdentifier(),
+                        DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()});
         assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
         assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
     }
@@ -513,6 +518,54 @@
                 OTHER_DEVICE_STATE.getIdentifier());
     }
 
+    @Test
+    public void requestState_flagCancelWhenRequesterNotOnTop_onDeviceSleep()
+            throws RemoteException {
+        requestState_flagCancelWhenRequesterNotOnTop_common(
+                // When the device is awake, the state should not change
+                () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(true),
+                // When the device is in sleep mode, the state should be canceled
+                () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(false)
+        );
+    }
+
+    @Test
+    public void requestState_flagCancelWhenRequesterNotOnTop_onKeyguardShow()
+            throws RemoteException {
+        requestState_flagCancelWhenRequesterNotOnTop_common(
+                // When the keyguard is not showing, the state should not change
+                () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(false),
+                // When the keyguard is showing, the state should be canceled
+                () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(true)
+        );
+    }
+
+    @Test
+    public void requestState_flagCancelWhenRequesterNotOnTop_onTaskStackChanged()
+            throws RemoteException {
+        requestState_flagCancelWhenRequesterNotOnTop_common(
+                // When the app is foreground, the state should not change
+                () -> {
+                    int pid = Binder.getCallingPid();
+                    when(mWindowProcessController.getPid()).thenReturn(pid);
+                    try {
+                        mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
+                // When the app is not foreground, the state should change
+                () -> {
+                    when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+                    try {
+                        mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+        );
+    }
+
     @FlakyTest(bugId = 200332057)
     @Test
     public void requestState_becomesUnsupported() throws RemoteException {
@@ -743,6 +796,84 @@
         Assert.assertTrue(Arrays.equals(expected, actual));
     }
 
+    /**
+     * Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
+     *
+     * The device state with FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP should be automatically canceled
+     * when certain events happen, e.g. when the top activity belongs to another app or when the
+     * device goes into the sleep mode.
+     *
+     * @param noChangeEvent an event that should not trigger auto cancellation of the state.
+     * @param autoCancelEvent an event that should trigger auto cancellation of the state.
+     * @throws RemoteException when the service throws exceptions.
+     */
+    private void requestState_flagCancelWhenRequesterNotOnTop_common(
+            Runnable noChangeEvent,
+            Runnable autoCancelEvent
+    ) throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+        flushHandler();
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestState(token,
+                DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(),
+                0 /* flags */);
+        flushHandler(2 /* count */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+
+        // Committed state changes as there is a requested override.
+        assertDeviceStateConditions(
+                DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+                DEFAULT_DEVICE_STATE, /* base state */
+                true /* isOverrideState */);
+
+        noChangeEvent.run();
+        flushHandler();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        assertDeviceStateConditions(
+                DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+                DEFAULT_DEVICE_STATE, /* base state */
+                true /* isOverrideState */);
+
+        autoCancelEvent.run();
+        flushHandler();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE,
+                false /* isOverrideState */);
+    }
+
+    /**
+     * Verify that the current device state and base state match the expected values.
+     *
+     * @param state the expected committed state.
+     * @param baseState the expected base state.
+     * @param isOverrideState whether a state override is active.
+     */
+    private void assertDeviceStateConditions(
+            DeviceState state, DeviceState baseState, boolean isOverrideState) {
+        assertEquals(mService.getCommittedState(), Optional.of(state));
+        assertEquals(mService.getBaseState(), Optional.of(baseState));
+        assertEquals(mSysPropSetter.getValue(),
+                state.getIdentifier() + ":" + state.getName());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                state.getIdentifier());
+        if (isOverrideState) {
+            // When a state override is active, the committed state should batch the override state.
+            assertEquals(mService.getOverrideState().get(), state);
+        } else {
+            // When there is no state override, the override state should be empty.
+            assertFalse(mService.getOverrideState().isPresent());
+        }
+    }
+
     private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
         private final DeviceStateProvider mProvider;
         private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
@@ -801,8 +932,10 @@
     }
 
     private static final class TestDeviceStateProvider implements DeviceStateProvider {
-        private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE,
-                OTHER_DEVICE_STATE };
+        private DeviceState[] mSupportedDeviceStates = new DeviceState[]{
+                DEFAULT_DEVICE_STATE,
+                OTHER_DEVICE_STATE,
+                DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
         private Listener mListener;
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index a9b7143..5ef762b 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -691,6 +691,7 @@
         // 2) Mark the displays as STATE_OFF so that it can continue with transition
         // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
         // 4) Dispatch handler events.
+        mLogicalDisplayMapper.onBootCompleted();
         mLogicalDisplayMapper.setDeviceStateLocked(0, false);
         mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
         advanceTime(1000);
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 3ce747f..e1a04ad5 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -25,6 +25,7 @@
 import android.hardware.BatteryState.STATUS_DISCHARGING
 import android.hardware.BatteryState.STATUS_FULL
 import android.hardware.BatteryState.STATUS_UNKNOWN
+import android.hardware.input.HostUsiVersion
 import android.hardware.input.IInputDeviceBatteryListener
 import android.hardware.input.IInputDeviceBatteryState
 import android.hardware.input.IInputDevicesChangedListener
@@ -86,7 +87,7 @@
         .setDescriptor("descriptor $deviceId")
         .setExternal(true)
         .setHasBattery(hasBattery)
-        .setSupportsUsi(supportsUsi)
+        .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null)
         .setGeneration(generation)
         .build()
 
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 0fd6a9e..a0f03bb 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -22,7 +22,6 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
 import static com.android.server.job.JobConcurrencyManager.workTypeToString;
@@ -59,7 +58,8 @@
 
     private static final double[] EQUAL_PROBABILITY_CDF =
             buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
-                    1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES);
+                    1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+                    1.0 / NUM_WORK_TYPES);
 
     private Random mRandom;
     private WorkCountTracker mWorkCountTracker;
@@ -72,8 +72,9 @@
 
     @NonNull
     private static double[] buildWorkTypeCdf(
-            double pTop, double pFgs, double pEj, double pBg, double pBgUserImp, double pBgUser) {
-        return buildCdf(pTop, pFgs, pEj, pBg, pBgUserImp, pBgUser);
+            double pTop, double pFgs, double pUi, double pEj, double pBg,
+            double pBgUserImp, double pBgUser) {
+        return buildCdf(pTop, pFgs, pUi, pEj, pBg, pBgUserImp, pBgUser);
     }
 
     @NonNull
@@ -108,23 +109,9 @@
 
     @JobConcurrencyManager.WorkType
     static int getRandomWorkType(double[] cdf, double rand) {
+        assertThat(cdf.length).isEqualTo(NUM_WORK_TYPES);
         final int index = getRandomIndex(cdf, rand);
-        switch (index) {
-            case 0:
-                return WORK_TYPE_TOP;
-            case 1:
-                return WORK_TYPE_FGS;
-            case 2:
-                return WORK_TYPE_EJ;
-            case 3:
-                return WORK_TYPE_BG;
-            case 4:
-                return WORK_TYPE_BGUSER_IMPORTANT;
-            case 5:
-                return WORK_TYPE_BGUSER;
-            default:
-                throw new IllegalStateException("Unknown work type");
-        }
+        return 1 << index;
     }
 
     /**
@@ -326,7 +313,7 @@
         final List<Pair<Integer, Float>> maxLimitRatios =
                 List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0, 0.5, 0, 0);
         final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
@@ -344,7 +331,7 @@
         final List<Pair<Integer, Float>> maxLimitRatios =
                 List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
         final double[] numTypesCdf = buildCdf(.75, .2, .05);
         final double probStart = 0.5;
 
@@ -362,7 +349,7 @@
         final List<Pair<Integer, Float>> maxLimitRatios =
                 List.of(Pair.create(WORK_TYPE_BG, .2f), Pair.create(WORK_TYPE_BGUSER, .1f));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
         final double[] numTypesCdf = buildCdf(.05, .95);
         final double probStart = 0.5;
 
@@ -382,7 +369,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 2.0f / 3));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, 0.02, .08);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.8, 0.02, .08);
         final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
@@ -402,7 +389,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.8, 0.05, 0.05, 0, 0.1, 0, 0);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -422,7 +409,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, 0.05, .75);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.1, 0.05, .75);
         final double[] numTypesCdf = buildCdf(0.5, 0.5);
         final double probStart = 0.5;
 
@@ -443,7 +430,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0, 0.05);
+        final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0, 0.05, 0, 0.05);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -464,7 +451,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0, 0.5);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.5, 0, 0.5);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -485,7 +472,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0, 0.9);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.1, 0, 0.9);
         final double[] numTypesCdf = buildCdf(0.9, 0.1);
         final double probStart = 0.5;
 
@@ -506,7 +493,7 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
         final double probStop = 0.5;
-        final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0, 0.1);
+        final double[] cdf = buildWorkTypeCdf(0, 0, .4, 0, 0.5, 0, 0.1);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -525,7 +512,7 @@
         final List<Pair<Integer, Float>> maxLimitRatios =
                 List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.25, 0.25, 0, 0, 0);
         final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
         final double probStart = 0.5;
 
@@ -566,7 +553,7 @@
         final List<Pair<Integer, Float>> maxLimitRatios =
                 List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0, 0.05);
+        final double[] cdf = buildWorkTypeCdf(.1, 0, 0.05, 0.45, 0.35, 0, 0.05);
         final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
@@ -586,7 +573,7 @@
                 List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 6));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0, 0.4);
+        final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.2, 0.2, 0.1, 0, 0.4);
         final double[] numTypesCdf = buildCdf(0.7, 0.3);
         final double probStart = 0.5;
 
@@ -607,7 +594,7 @@
                         Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 7),
                         Pair.create(WORK_TYPE_BGUSER, 1.0f / 7));
         final double probStop = 0.4;
-        final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.25, 0.05, 0.3, 0.3);
+        final double[] cdf = buildWorkTypeCdf(0.01, 0.02, 0.09, 0.25, 0.05, 0.3, 0.3);
         final double[] numTypesCdf = buildCdf(0.7, 0.3);
         final double probStart = 0.5;
 
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index bd5a063..94dfae3 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
 import static com.android.server.job.JobConcurrencyManager.workTypeToString;
 
 import static org.junit.Assert.assertEquals;
@@ -47,6 +48,7 @@
     private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
     private static final String KEY_MAX_RATIO_TOP = "concurrency_max_ratio_top_test";
     private static final String KEY_MAX_RATIO_FGS = "concurrency_max_ratio_fgs_test";
+    private static final String KEY_MAX_RATIO_UI = "concurrency_max_ratio_ui_test";
     private static final String KEY_MAX_RATIO_EJ = "concurrency_max_ratio_ej_test";
     private static final String KEY_MAX_RATIO_BG = "concurrency_max_ratio_bg_test";
     private static final String KEY_MAX_RATIO_BGUSER_IMPORTANT =
@@ -54,6 +56,7 @@
     private static final String KEY_MAX_RATIO_BGUSER = "concurrency_max_ratio_bguser_test";
     private static final String KEY_MIN_RATIO_TOP = "concurrency_min_ratio_top_test";
     private static final String KEY_MIN_RATIO_FGS = "concurrency_min_ratio_fgs_test";
+    private static final String KEY_MIN_RATIO_UI = "concurrency_min_ratio_ui_test";
     private static final String KEY_MIN_RATIO_EJ = "concurrency_min_ratio_ej_test";
     private static final String KEY_MIN_RATIO_BG = "concurrency_min_ratio_bg_test";
     private static final String KEY_MIN_RATIO_BGUSER_IMPORTANT =
@@ -326,31 +329,35 @@
                 /* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16)));
 
         check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt(KEY_MAX_TOTAL, 16)
+                        .setInt(KEY_MAX_TOTAL, 32)
                         .setFloat(KEY_MAX_RATIO_TOP, 1f)
-                        .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 16)
-                        .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 16)
-                        .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 16)
-                        .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 16)
-                        .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 16)
-                        .setFloat(KEY_MAX_RATIO_BG, 13.0f / 16)
-                        .setFloat(KEY_MIN_RATIO_BG, 3.0f / 16)
-                        .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 16)
-                        .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 16)
-                        .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 16)
-                        .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 16)
+                        .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_UI, 10.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_UI, 4.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_BG, 13.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_BG, 3.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 32)
+                        .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 32)
+                        .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 32)
                         .build(),
-                /* limit */ 16,
+                /* limit */ 32,
                 /*default*/ 9,
                 /* min */ List.of(Pair.create(WORK_TYPE_BG, .99f)),
                 /* max */ List.of(Pair.create(WORK_TYPE_BG, 1f)),
-                /*expected*/ true, 16,
+                /*expected*/ true, 32,
                 /* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2),
+                        Pair.create(WORK_TYPE_UI, 4),
                         Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 3),
                         Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
                         Pair.create(WORK_TYPE_BGUSER, 2)),
                 /* max */
-                List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15),
+                List.of(Pair.create(WORK_TYPE_TOP, 32), Pair.create(WORK_TYPE_FGS, 15),
+                        Pair.create(WORK_TYPE_UI, 10),
                         Pair.create(WORK_TYPE_EJ, 14), Pair.create(WORK_TYPE_BG, 13),
                         Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 12),
                         Pair.create(WORK_TYPE_BGUSER, 11)));
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index fa8d866..93f6db7 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -500,6 +500,7 @@
         // The cached conversations are above the limit because every conversation has active
         // notifications. To uncache one of them, the notifications for that conversation need to
         // be dismissed.
+        String notificationKey = "";
         for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
             String shortcutId = TEST_SHORTCUT_ID + i;
             ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
@@ -507,11 +508,13 @@
             shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
             mDataManager.addOrUpdateConversationInfo(shortcut);
             when(mNotification.getShortcutId()).thenReturn(shortcutId);
-            sendGenericNotification();
+            notificationKey = String.format("notification-key-%d", i);
+            sendGenericNotificationWithKey(notificationKey);
         }
 
         // Post another notification for the last conversation.
-        sendGenericNotification();
+        String otherNotificationKey = "other-notification-key";
+        sendGenericNotificationWithKey(otherNotificationKey);
 
         // Removing one of the two notifications does not un-cache the shortcut.
         listenerService.onNotificationRemoved(mGenericSbn, null,
@@ -520,6 +523,7 @@
                 anyInt(), any(), anyString(), any(), anyInt(), anyInt());
 
         // Removing the second notification un-caches the shortcut.
+        when(mGenericSbn.getKey()).thenReturn(notificationKey);
         listenerService.onNotificationRemoved(mGenericSbn, null,
                 NotificationListenerService.REASON_CANCEL_ALL);
         verify(mShortcutServiceInternal).uncacheShortcuts(
@@ -687,6 +691,35 @@
     }
 
     @Test
+    public void testGetConversation_trackActiveConversations() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNull();
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+
+        sendGenericNotification();
+        sendGenericNotification();
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertTrue(result.hasActiveNotifications());
+
+        // Both generic notifications have the same notification key, so a single dismiss will
+        // remove both of them.
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationRemoved(mGenericSbn, null,
+                NotificationListenerService.REASON_CANCEL);
+        ConversationChannel resultTwo = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertFalse(resultTwo.hasActiveNotifications());
+    }
+
+    @Test
     public void testGetConversation_unsyncedShortcut() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -1322,7 +1355,7 @@
 
         sendGenericNotification();
 
-       mDataManager.getRecentConversations(USER_ID_PRIMARY);
+        mDataManager.getRecentConversations(USER_ID_PRIMARY);
 
         verify(mShortcutServiceInternal).getShortcuts(
                 anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1693,6 +1726,12 @@
     // "Sends" a notification to a non-customized notification channel - the notification channel
     // is something generic like "messages" and the notification has a  shortcut id
     private void sendGenericNotification() {
+        sendGenericNotificationWithKey(GENERIC_KEY);
+    }
+
+    // "Sends" a notification to a non-customized notification channel with the specified key.
+    private void sendGenericNotificationWithKey(String key) {
+        when(mGenericSbn.getKey()).thenReturn(key);
         when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
         doAnswer(invocationOnMock -> {
             NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
@@ -1708,7 +1747,7 @@
                     mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
                     false, false, false, null, 0, false, 0);
             return true;
-        }).when(mRankingMap).getRanking(eq(GENERIC_KEY),
+        }).when(mRankingMap).getRanking(eq(key),
                 any(NotificationListenerService.Ranking.class));
         NotificationListenerService listenerService =
                 mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 76a13f1..1305e07 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -26,10 +26,8 @@
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
@@ -41,6 +39,7 @@
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import androidx.annotation.Nullable;
@@ -48,6 +47,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Range;
 
@@ -56,16 +57,14 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import javax.annotation.concurrent.GuardedBy;
-
 /** Test {@link UserManager} functionality. */
 @Postsubmit
 @RunWith(AndroidJUnit4.class)
@@ -73,8 +72,6 @@
     // Taken from UserManagerService
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // 30 years
 
-    private static final int REMOVE_CHECK_INTERVAL_MILLIS = 500; // 0.5 seconds
-    private static final int REMOVE_TIMEOUT_MILLIS = 60 * 1000; // 60 seconds
     private static final int SWITCH_USER_TIMEOUT_SECONDS = 40; // 40 seconds
 
     // Packages which are used during tests.
@@ -86,12 +83,10 @@
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
-    private final Object mUserRemoveLock = new Object();
-
     private UserManager mUserManager = null;
     private ActivityManager mActivityManager;
     private PackageManager mPackageManager;
-    private List<Integer> usersToRemove;
+    private ArraySet<Integer> mUsersToRemove;
     private UserSwitchWaiter mUserSwitchWaiter;
 
     @Before
@@ -101,30 +96,16 @@
         mPackageManager = mContext.getPackageManager();
         mUserSwitchWaiter = new UserSwitchWaiter(TAG, SWITCH_USER_TIMEOUT_SECONDS);
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                switch (intent.getAction()) {
-                    case Intent.ACTION_USER_REMOVED:
-                        synchronized (mUserRemoveLock) {
-                            mUserRemoveLock.notifyAll();
-                        }
-                        break;
-                }
-            }
-        }, filter);
-
+        mUsersToRemove = new ArraySet<>();
         removeExistingUsers();
-        usersToRemove = new ArrayList<>();
     }
 
     @After
     public void tearDown() throws Exception {
         mUserSwitchWaiter.close();
-        for (Integer userId : usersToRemove) {
-            removeUser(userId);
-        }
+
+        // Making a copy of mUsersToRemove to avoid ConcurrentModificationException
+        mUsersToRemove.stream().toList().forEach(this::removeUser);
     }
 
     private void removeExistingUsers() {
@@ -406,13 +387,11 @@
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
                 asHandle(currentUser));
         try {
-            synchronized (mUserRemoveLock) {
+            runThenWaitForUserRemoval(() -> {
                 assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
                         /* overrideDevicePolicy= */ true))
-                                .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-                waitForUserRemovalLocked(user1.id);
-            }
-
+                        .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+            }, user1.id); // wait for user removal
         } finally {
             mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
                     asHandle(currentUser));
@@ -477,13 +456,12 @@
         assertThat(hasUser(user1.id)).isTrue();
         assertThat(getUser(user1.id).isEphemeral()).isTrue();
 
-        // Switch back to the starting user.
-        switchUser(startUser);
+        runThenWaitForUserRemoval(() -> {
+            // Switch back to the starting user.
+            switchUser(startUser);
+            // User will be removed once switch is complete
+        }, user1.id); // wait for user removal
 
-        // User is removed once switch is complete
-        synchronized (mUserRemoveLock) {
-            waitForUserRemovalLocked(user1.id);
-        }
         assertThat(hasUser(user1.id)).isFalse();
     }
 
@@ -495,19 +473,19 @@
         // Switch to the user just created.
         switchUser(testUser.id);
 
-        switchUserThenRun(startUser, () -> {
-            // While the user switch is happening, call removeUserWhenPossible for the current user.
-            assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
-                    .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+        runThenWaitForUserRemoval(() -> {
+            switchUserThenRun(startUser, () -> {
+                // While the switch is happening, call removeUserWhenPossible for the current user.
+                assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
+                        /* overrideDevicePolicy= */ false))
+                        .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
 
-            assertThat(hasUser(testUser.id)).isTrue();
-            assertThat(getUser(testUser.id).isEphemeral()).isTrue();
-        });
+                assertThat(hasUser(testUser.id)).isTrue();
+                assertThat(getUser(testUser.id).isEphemeral()).isTrue();
+            }); // wait for user switch - startUser
+            // User will be removed once switch is complete
+        }, testUser.id); // wait for user removal
 
-        // User is removed once switch is complete
-        synchronized (mUserRemoveLock) {
-            waitForUserRemovalLocked(testUser.id);
-        }
         assertThat(hasUser(testUser.id)).isFalse();
     }
 
@@ -519,20 +497,20 @@
 
         switchUserThenRun(testUser.id, () -> {
             // While the user switch is happening, call removeUserWhenPossible for the target user.
-            assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
+            assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
+                    /* overrideDevicePolicy= */ false))
                     .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
 
             assertThat(hasUser(testUser.id)).isTrue();
             assertThat(getUser(testUser.id).isEphemeral()).isTrue();
-        });
+        }); // wait for user switch - testUser
 
-        // Switch back to the starting user.
-        switchUser(startUser);
+        runThenWaitForUserRemoval(() -> {
+            // Switch back to the starting user.
+            switchUser(startUser);
+            // User will be removed once switch is complete
+        }, testUser.id); // wait for user removal
 
-        // User is removed once switch is complete
-        synchronized (mUserRemoveLock) {
-            waitForUserRemovalLocked(testUser.id);
-        }
         assertThat(hasUser(testUser.id)).isFalse();
     }
 
@@ -540,12 +518,12 @@
     @Test
     public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
-        synchronized (mUserRemoveLock) {
+
+        runThenWaitForUserRemoval(() -> {
             assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
                     /* overrideDevicePolicy= */ false))
-                            .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-            waitForUserRemovalLocked(user1.id);
-        }
+                    .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+        }, user1.id); // wait for user removal
 
         assertThat(hasUser(user1.id)).isFalse();
     }
@@ -562,12 +540,12 @@
         final UserInfo workProfileUser = createProfileForUser("Work Profile user",
                 UserManager.USER_TYPE_PROFILE_MANAGED,
                 parentUser.id);
-        synchronized (mUserRemoveLock) {
+
+        runThenWaitForUserRemoval(() -> {
             assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
                     /* overrideDevicePolicy= */ false))
                     .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-            waitForUserRemovalLocked(parentUser.id);
-        }
+        }, parentUser.id); // wait for user removal
 
         assertThat(hasUser(parentUser.id)).isFalse();
         assertThat(hasUser(cloneProfileUser.id)).isFalse();
@@ -1298,9 +1276,7 @@
                 UserInfo user = mUserManager.createUser(userName, 0);
                 if (user != null) {
                     created.incrementAndGet();
-                    synchronized (mUserRemoveLock) {
-                        usersToRemove.add(user.id);
-                    }
+                    mUsersToRemove.add(user.id);
                 }
             });
         }
@@ -1462,40 +1438,41 @@
         }, () -> fail("Could not complete switching to user " + userId));
     }
 
-    private void removeUser(UserHandle user) {
-        synchronized (mUserRemoveLock) {
-            mUserManager.removeUser(user);
-            waitForUserRemovalLocked(user.getIdentifier());
-        }
+    private void removeUser(UserHandle userHandle) {
+        runThenWaitForUserRemoval(
+                () -> mUserManager.removeUser(userHandle),
+                userHandle == null ? UserHandle.USER_NULL : userHandle.getIdentifier()
+        );
     }
 
     private void removeUser(int userId) {
-        synchronized (mUserRemoveLock) {
-            mUserManager.removeUser(userId);
-            waitForUserRemovalLocked(userId);
-        }
+        runThenWaitForUserRemoval(
+                () -> mUserManager.removeUser(userId),
+                userId
+        );
     }
 
-    @GuardedBy("mUserRemoveLock")
-    private void waitForUserRemovalLocked(int userId) {
-        long time = System.currentTimeMillis();
-        while (mUserManager.getUserInfo(userId) != null) {
-            try {
-                mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS);
-            } catch (InterruptedException ie) {
-                Thread.currentThread().interrupt();
-                return;
-            }
-            if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) {
-                fail("Timeout waiting for removeUser. userId = " + userId);
-            }
+    private void runThenWaitForUserRemoval(Runnable runnable, int userIdToWaitUntilDeleted) {
+        Function<Intent, Boolean> checker = intent -> {
+            UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
+            return userHandle != null && userHandle.getIdentifier() == userIdToWaitUntilDeleted;
+        };
+
+        BlockingBroadcastReceiver blockingBroadcastReceiver = BlockingBroadcastReceiver.create(
+                mContext, Intent.ACTION_USER_REMOVED, checker);
+
+        blockingBroadcastReceiver.register();
+
+        try (blockingBroadcastReceiver) {
+            runnable.run();
         }
+        mUsersToRemove.remove(userIdToWaitUntilDeleted);
     }
 
     private UserInfo createUser(String name, int flags) {
         UserInfo user = mUserManager.createUser(name, flags);
         if (user != null) {
-            usersToRemove.add(user.id);
+            mUsersToRemove.add(user.id);
         }
         return user;
     }
@@ -1503,7 +1480,7 @@
     private UserInfo createUser(String name, String userType, int flags) {
         UserInfo user = mUserManager.createUser(name, userType, flags);
         if (user != null) {
-            usersToRemove.add(user.id);
+            mUsersToRemove.add(user.id);
         }
         return user;
     }
@@ -1517,7 +1494,7 @@
         UserInfo profile = mUserManager.createProfileForUser(
                 name, userType, 0, userHandle, disallowedPackages);
         if (profile != null) {
-            usersToRemove.add(profile.id);
+            mUsersToRemove.add(profile.id);
         }
         return profile;
     }
@@ -1527,7 +1504,7 @@
         UserInfo profile = mUserManager.createProfileForUserEvenWhenDisallowed(
                 name, userType, 0, userHandle, null);
         if (profile != null) {
-            usersToRemove.add(profile.id);
+            mUsersToRemove.add(profile.id);
         }
         return profile;
     }
@@ -1535,7 +1512,7 @@
     private UserInfo createRestrictedProfile(String name) {
         UserInfo profile = mUserManager.createRestrictedProfile(name);
         if (profile != null) {
-            usersToRemove.add(profile.id);
+            mUsersToRemove.add(profile.id);
         }
         return profile;
     }
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 261156611..7fac9b6 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,6 +35,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.os.PowerManager;
 
 import androidx.annotation.NonNull;
 
@@ -312,6 +313,14 @@
                 + "            </sensor>\n"
                 + "        </conditions>\n"
                 + "    </device-state>\n"
+                + "    <device-state>\n"
+                + "        <identifier>4</identifier>\n"
+                + "        <name>THERMAL_TEST</name>\n"
+                + "        <flags>\n"
+                + "            <flag>FLAG_EMULATED_ONLY</flag>\n"
+                + "            <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+                + "        </flags>\n"
+                + "    </device-state>\n"
                 + "</device-state-config>\n";
         DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
         return DeviceStateProviderImpl.createFromConfig(mContext,
@@ -332,7 +341,10 @@
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
                         new DeviceState(2, "HALF_OPENED", 0 /* flags */),
-                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
                 mDeviceStateArrayCaptor.getValue());
         // onStateChanged() should not be called because the provider has not yet been notified of
         // the initial sensor state.
@@ -376,6 +388,57 @@
     }
 
     @Test
+    public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+        Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+        when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+        DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+        provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+                mDeviceStateArrayCaptor.getValue());
+        Mockito.clearInvocations(listener);
+
+        provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        Mockito.clearInvocations(listener);
+
+        // The THERMAL_TEST state should be disabled.
+        provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */) },
+                mDeviceStateArrayCaptor.getValue());
+        Mockito.clearInvocations(listener);
+
+        // The THERMAL_TEST state should be re-enabled.
+        provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        assertArrayEquals(
+                new DeviceState[]{
+                        new DeviceState(1, "CLOSED", 0 /* flags */),
+                        new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+                        new DeviceState(3, "OPENED", 0 /* flags */),
+                        new DeviceState(4, "THERMAL_TEST",
+                                DeviceState.FLAG_EMULATED_ONLY
+                                        | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+                mDeviceStateArrayCaptor.getValue());
+    }
+
+    @Test
     public void test_invalidSensorValues() throws Exception {
         // onStateChanged() should not be triggered by invalid sensor values.
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index c2556e9..34e45c2 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -25,6 +25,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.os.Handler;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -35,6 +36,7 @@
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -55,12 +57,13 @@
     private static final int TEST_UID_5 = 76421423;
 
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private final Handler mHandler = Mockito.mock(Handler.class);
     private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
 
     @Test
     public void removesOldWakeups() {
         // The xml resource doesn't matter for this test.
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
 
         final Set<Long> timestamps = new HashSet<>();
         final long firstWakeup = 453192;
@@ -88,7 +91,7 @@
 
     @Test
     public void alarmIrqAttributionSolo() {
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 12423121;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
@@ -113,7 +116,7 @@
 
     @Test
     public void alarmIrqAttributionCombined() {
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 92123210;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 4,
@@ -143,7 +146,7 @@
 
     @Test
     public void unknownIrqAttribution() {
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 92123410;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
@@ -163,7 +166,7 @@
 
     @Test
     public void unknownAttribution() {
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
         final long wakeupTime = 72123210;
 
         obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
@@ -178,7 +181,7 @@
 
     @Test
     public void unsupportedAttribution() {
-        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
 
         long wakeupTime = 970934;
         obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index caaf934..1deb58e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -71,6 +71,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -749,16 +750,15 @@
     }
 
     /**
-     * This test ensures that supported usecases aren't aborted when background starts are
-     * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
-     * this case the real calling process (pending intent) has a visible window.
+     * The sending app has a visible window, but does not (by default) allow the pending intent to
+     * start the background activity.
      */
     @Test
-    public void
-            testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowNotAborted() {
+    public void testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowAborted() {
         doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+
         runAndVerifyBackgroundActivityStartsSubtest(
-                "disallowed_realCallingUidHasVisibleWindow_notAborted", false,
+                "disallowed_realCallingUidHasVisibleWindow_abortedInU", true,
                 UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
                 UNIMPORTANT_UID2, true, PROCESS_STATE_BOUND_TOP,
                 false, false, false, false, false, false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 0231b46..21135e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -564,20 +564,23 @@
         final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
                 "pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
         doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
-        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
+        app.setVisibleRequested(true);
         final TransitionController controller = app.mTransitionController;
         final Transition transition = controller.createTransition(TRANSIT_OPEN);
         final RemoteTransition remoteTransition = new RemoteTransition(
                 mock(IRemoteTransition.class));
         remoteTransition.setAppThread(delegateProc.getThread());
-        transition.collectExistenceChange(app.getTask());
-        controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+        transition.collect(app);
+        controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
                 null /* displayChange */);
         testPlayer.startTransition();
         testPlayer.onTransactionReady(app.getSyncTransaction());
         assertTrue(playerProc.isRunningRemoteTransition());
         assertTrue(delegateProc.isRunningRemoteTransition());
         assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+        assertTrue(app.isVisible());
 
         testPlayer.finish();
         assertFalse(playerProc.isRunningRemoteTransition());
@@ -1114,6 +1117,14 @@
 
         assertFalse(activity1.isVisible());
         assertTrue(activity2.isVisible());
+
+        // The abort should still commit visible-requested to visible.
+        final Transition abortTransition = controller.createTransition(TRANSIT_OPEN);
+        abortTransition.collect(activity1);
+        activity1.setVisibleRequested(true);
+        activity1.setVisible(false);
+        abortTransition.abort();
+        assertTrue(activity1.isVisible());
     }
 
     @Test
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 04c8f8f..127195c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,7 +21,7 @@
 import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
 import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
+import static android.service.voice.VoiceInteractionSession.KEY_SHOW_SESSION_ID;
 
 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
 
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 770a374..315ac67 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,6 +21,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.OutcomeReceiver;
@@ -45,7 +46,8 @@
  * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
  * the operation failed.
  */
-public final class CallControl implements AutoCloseable {
+@SuppressLint("NotCloseable")
+public final class CallControl {
     private static final String TAG = CallControl.class.getSimpleName();
     private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
     private final String mCallId;
@@ -261,17 +263,6 @@
     }
 
     /**
-     * This method should be called after
-     * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
-     * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
-     * to destroy all references of this object and avoid memory leaks.
-     */
-    @Override
-    public void close() {
-        mRepository.removeCallFromServiceWrapper(mPhoneAccountHandle, mCallId);
-    }
-
-    /**
      * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
      * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
      * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 95a8e16..fd2907c 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -32,8 +32,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A unified virtual device providing a means of voice (and other) communication on a device.
@@ -148,6 +150,14 @@
 
     private final Object mLock = new Object();
 
+    // Future used to delay terminating the InCallService before the call disconnect tone
+    // finishes playing.
+    private static Map<String, CompletableFuture<Void>> sDisconnectedToneFutures = new ArrayMap<>();
+
+    // Timeout value to be used to ensure future completion for sDisconnectedToneFutures. This is
+    // set to 4 seconds to account for the exceptional case (TONE_CONGESTION).
+    private static final int DISCONNECTED_TONE_TIMEOUT = 4000;
+
     Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) {
         mInCallAdapter = adapter;
         mCallingPackage = callingPackage;
@@ -456,9 +466,45 @@
     }
 
     private void fireCallRemoved(Call call) {
-        for (Listener listener : mListeners) {
-            listener.onCallRemoved(this, call);
+        String callId = call.internalGetCallId();
+        CompletableFuture<Void> disconnectedToneFuture = initializeDisconnectedToneFuture(callId);
+        // delay the InCallService termination until after the disconnect tone finishes playing
+        disconnectedToneFuture.thenRunAsync(() -> {
+            for (Listener listener : mListeners) {
+                listener.onCallRemoved(this, call);
+            }
+            // clean up the future after
+            sDisconnectedToneFutures.remove(callId);
+        });
+    }
+
+    /**
+     * Initialize disconnect tone future to be used in delaying ICS termination.
+     *
+     * @return CompletableFuture to delay InCallService termination until after the disconnect tone
+     * finishes playing. A timeout of 4s is used to handle the use case when we play
+     * TONE_CONGESTION and to ensure completion so that we don't block the removal of the service.
+     */
+    private CompletableFuture<Void> initializeDisconnectedToneFuture(String callId) {
+        // create the future and map (sDisconnectedToneFutures) it to the corresponding call id
+        CompletableFuture<Void> disconnectedToneFuture = new CompletableFuture<Void>()
+                .completeOnTimeout(null, DISCONNECTED_TONE_TIMEOUT, TimeUnit.MILLISECONDS);
+        // we should not encounter duplicate insertions since call ids are unique
+        sDisconnectedToneFutures.put(callId, disconnectedToneFuture);
+        return disconnectedToneFuture;
+    }
+
+    /**
+     * Completes disconnected tone future with passed in result.
+     * @hide
+     * @return true if future was completed, false otherwise
+     */
+    public static boolean completeDisconnectedToneFuture(String callId) {
+        if (sDisconnectedToneFutures.containsKey(callId)) {
+            sDisconnectedToneFutures.get(callId).complete(null);
+            return true;
         }
+        return false;
     }
 
     private void fireCallAudioStateChanged(CallAudioState audioState) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b55a29c..7950351 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7510,6 +7510,48 @@
         public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
                 KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
 
+        /** @hide */
+        @IntDef({
+            VOWIFI_REQUIRES_NONE,
+            VOWIFI_REQUIRES_SETTING_ENABLED,
+            VOWIFI_REQUIRES_VALID_EID,
+        })
+        public @interface VoWiFiRequires {}
+
+        /**
+         * Default value.
+         * If {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true},
+         * VoWi-Fi emergency call shall be attempted if Wi-Fi network is connected.
+         * Otherwise, it shall be attempted if IMS is registered over Wi-Fi.
+         * @hide
+         */
+        public static final int VOWIFI_REQUIRES_NONE = 0;
+
+        /**
+         * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+         * and Wi-Fi calling setting is enabled. This value is applicable if the value of
+         * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+         * @hide
+         */
+        public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1;
+
+        /**
+         * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+         * and Wi-Fi calling is activated successfully. This value is applicable if the value of
+         * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+         * @hide
+         */
+        public static final int VOWIFI_REQUIRES_VALID_EID = 2;
+
+        /**
+         * Specifies the condition when emergency call shall be attempted on IMS over Wi-Fi.
+         *
+         * The default value for this key is {@code #VOWIFI_REQUIRES_NONE}.
+         * @hide
+         */
+        public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT =
+                KEY_PREFIX + "emergency_vowifi_requires_condition_int";
+
         /**
          * Specifies maximum number of emergency call retries over Wi-Fi.
          * This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
@@ -7621,7 +7663,8 @@
                 KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
 
         /**
-         * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+         * Specifies if emergency call shall be attempted on IMS over cellular network
+         * only when VoLTE is enabled.
          *
          * The default value for this key is {@code false}.
          * @hide
@@ -7685,6 +7728,7 @@
                     });
 
             defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+            defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);
             defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
             defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
             defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a965ba..c47c67e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3242,17 +3242,17 @@
             case NETWORK_TYPE_CDMA:
                 return "CDMA";
             case NETWORK_TYPE_EVDO_0:
-                return "EVDO-0";
+                return "CDMA - EvDo rev. 0";
             case NETWORK_TYPE_EVDO_A:
-                return "EVDO-A";
+                return "CDMA - EvDo rev. A";
             case NETWORK_TYPE_EVDO_B:
-                return "EVDO-B";
+                return "CDMA - EvDo rev. B";
             case NETWORK_TYPE_1xRTT:
-                return "1xRTT";
+                return "CDMA - 1xRTT";
             case NETWORK_TYPE_LTE:
                 return "LTE";
             case NETWORK_TYPE_EHRPD:
-                return "eHRPD";
+                return "CDMA - eHRPD";
             case NETWORK_TYPE_IDEN:
                 return "iDEN";
             case NETWORK_TYPE_HSPAP:
@@ -3260,7 +3260,7 @@
             case NETWORK_TYPE_GSM:
                 return "GSM";
             case NETWORK_TYPE_TD_SCDMA:
-                return "TD-SCDMA";
+                return "TD_SCDMA";
             case NETWORK_TYPE_IWLAN:
                 return "IWLAN";
             case NETWORK_TYPE_LTE_CA:
@@ -9574,10 +9574,10 @@
      */
     public static boolean isValidAllowedNetworkTypesReason(@AllowedNetworkTypesReason int reason) {
         switch (reason) {
-            case ALLOWED_NETWORK_TYPES_REASON_USER:
-            case ALLOWED_NETWORK_TYPES_REASON_POWER:
-            case ALLOWED_NETWORK_TYPES_REASON_CARRIER:
-            case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
             case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
                 return true;
         }
@@ -15002,6 +15002,14 @@
     @TestApi
     public static final int HAL_SERVICE_IMS = 7;
 
+    /**
+     * HAL service type that supports the HAL APIs implementation of IRadioSatellite
+     * {@link RadioSatelliteProxy}
+     * @hide
+     */
+    @TestApi
+    public static final int HAL_SERVICE_SATELLITE = 8;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"HAL_SERVICE_"},
@@ -15014,6 +15022,7 @@
                     HAL_SERVICE_SIM,
                     HAL_SERVICE_VOICE,
                     HAL_SERVICE_IMS,
+                    HAL_SERVICE_SATELLITE
             })
     public @interface HalService {}
 
@@ -18034,45 +18043,4 @@
                 return "UNKNOWN(" + state + ")";
         }
     }
-
-    /**
-     * Convert the allowed network types reason to string.
-     *
-     * @param reason The allowed network types reason.
-     * @return The converted string.
-     *
-     * @hide
-     */
-    @NonNull
-    public static String allowedNetworkTypesReasonToString(@AllowedNetworkTypesReason int reason) {
-        switch (reason) {
-            case ALLOWED_NETWORK_TYPES_REASON_USER: return "user";
-            case ALLOWED_NETWORK_TYPES_REASON_POWER: return "power";
-            case ALLOWED_NETWORK_TYPES_REASON_CARRIER: return "carrier";
-            case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: return "enable_2g";
-            case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS: return "user_restrictions";
-            default: return "unknown(" + reason + ")";
-        }
-    }
-
-    /**
-     * Convert the allowed network types reason from string.
-     *
-     * @param reason The reason in string format.
-     * @return The allowed network types reason.
-     *
-     * @hide
-     */
-    @AllowedNetworkTypesReason
-    public static int allowedNetworkTypesReasonFromString(@NonNull String reason) {
-        switch (reason) {
-            case "user": return ALLOWED_NETWORK_TYPES_REASON_USER;
-            case "power": return ALLOWED_NETWORK_TYPES_REASON_POWER;
-            case "carrier": return ALLOWED_NETWORK_TYPES_REASON_CARRIER;
-            case "enable_2g": return ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G;
-            case "user_restrictions": return ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS;
-            default: throw new IllegalArgumentException("allowedNetworkTypesReasonFromString: "
-                    + "invalid reason " + reason);
-        }
-    }
 }
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
new file mode 100644
index 0000000..d6dd57a
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class PointingInfo implements Parcelable {
+    /** Satellite azimuth in degrees */
+    private float mSatelliteAzimuthDegrees;
+
+    /** Satellite elevation in degrees */
+    private float mSatelliteElevationDegrees;
+
+    /** Antenna azimuth in degrees */
+    private float mAntennaAzimuthDegrees;
+
+    /**
+     * Angle of rotation about the x axis. This value represents the angle between a plane
+     * parallel to the device's screen and a plane parallel to the ground.
+     */
+    private float mAntennaPitchDegrees;
+
+    /**
+     * Angle of rotation about the y axis. This value represents the angle between a plane
+     * perpendicular to the device's screen and a plane parallel to the ground.
+     */
+    private float mAntennaRollDegrees;
+
+    /**
+     * @hide
+     */
+    public PointingInfo(float satelliteAzimuthDegress, float satelliteElevationDegress,
+            float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+        mSatelliteAzimuthDegrees = satelliteAzimuthDegress;
+        mSatelliteElevationDegrees = satelliteElevationDegress;
+        mAntennaAzimuthDegrees = antennaAzimuthDegrees;
+        mAntennaPitchDegrees = antennaPitchDegrees;
+        mAntennaRollDegrees = antennaRollDegrees;
+    }
+
+    private PointingInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mSatelliteAzimuthDegrees);
+        out.writeFloat(mSatelliteElevationDegrees);
+        out.writeFloat(mAntennaAzimuthDegrees);
+        out.writeFloat(mAntennaPitchDegrees);
+        out.writeFloat(mAntennaRollDegrees);
+    }
+
+    public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
+            new Creator<PointingInfo>() {
+                @Override
+                public PointingInfo createFromParcel(Parcel in) {
+                    return new PointingInfo(in);
+                }
+
+                @Override
+                public PointingInfo[] newArray(int size) {
+                    return new PointingInfo[size];
+                }
+            };
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SatelliteAzimuthDegrees:");
+        sb.append(mSatelliteAzimuthDegrees);
+        sb.append(",");
+
+        sb.append("SatelliteElevationDegrees:");
+        sb.append(mSatelliteElevationDegrees);
+        sb.append(",");
+
+        sb.append("AntennaAzimuthDegrees:");
+        sb.append(mAntennaAzimuthDegrees);
+        sb.append(",");
+
+        sb.append("AntennaPitchDegrees:");
+        sb.append(mAntennaPitchDegrees);
+        sb.append(",");
+
+        sb.append("AntennaRollDegrees:");
+        sb.append(mAntennaRollDegrees);
+        return sb.toString();
+    }
+
+    public float getSatelliteAzimuthDegrees() {
+        return mSatelliteAzimuthDegrees;
+    }
+
+    public float getSatelliteElevationDegrees() {
+        return mSatelliteElevationDegrees;
+    }
+
+    public float getAntennaAzimuthDegrees() {
+        return mAntennaAzimuthDegrees;
+    }
+
+    public float getAntennaPitchDegrees() {
+        return mAntennaPitchDegrees;
+    }
+
+    public float getAntennaRollDegrees() {
+        return mAntennaRollDegrees;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSatelliteAzimuthDegrees = in.readFloat();
+        mSatelliteElevationDegrees = in.readFloat();
+        mAntennaAzimuthDegrees = in.readFloat();
+        mAntennaPitchDegrees = in.readFloat();
+        mAntennaRollDegrees = in.readFloat();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
new file mode 100644
index 0000000..c5ae4db
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class SatelliteCapabilities implements Parcelable {
+    /**
+     * List of technologies supported by the satellite modem.
+     */
+    private Set<Integer> mSupportedRadioTechnologies;
+
+    /**
+     * Whether satellite mode is always on (this to indicate power impact of keeping it on is
+     * very minimal).
+     */
+    private boolean mIsAlwaysOn;
+
+    /**
+     * Whether UE needs to point to a satellite to send and receive data.
+     */
+    private boolean mNeedsPointingToSatellite;
+
+    /**
+     * List of features supported by the Satellite modem.
+     */
+    private Set<Integer> mSupportedFeatures;
+
+    /**
+     * Whether UE needs a separate SIM profile to communicate with the Satellite network.
+     */
+    private boolean mNeedsSeparateSimProfile;
+
+    /**
+     * @hide
+     */
+    public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
+            boolean needsPointingToSatellite, Set<Integer> supportedFeatures,
+            boolean needsSeparateSimProfile) {
+        mSupportedRadioTechnologies = supportedRadioTechnologies;
+        mIsAlwaysOn = isAlwaysOn;
+        mNeedsPointingToSatellite = needsPointingToSatellite;
+        mSupportedFeatures = supportedFeatures;
+        mNeedsSeparateSimProfile = needsSeparateSimProfile;
+    }
+
+    private SatelliteCapabilities(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+            out.writeInt(mSupportedRadioTechnologies.size());
+            for (int technology : mSupportedRadioTechnologies) {
+                out.writeInt(technology);
+            }
+        } else {
+            out.writeInt(0);
+        }
+
+        out.writeBoolean(mIsAlwaysOn);
+        out.writeBoolean(mNeedsPointingToSatellite);
+
+        if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+            out.writeInt(mSupportedFeatures.size());
+            for (int feature : mSupportedFeatures) {
+                out.writeInt(feature);
+            }
+        } else {
+            out.writeInt(0);
+        }
+
+        out.writeBoolean(mNeedsSeparateSimProfile);
+    }
+
+    public static final @android.annotation.NonNull Creator<SatelliteCapabilities> CREATOR =
+            new Creator<SatelliteCapabilities>() {
+                @Override
+                public SatelliteCapabilities createFromParcel(Parcel in) {
+                    return new SatelliteCapabilities(in);
+                }
+
+                @Override
+                public SatelliteCapabilities[] newArray(int size) {
+                    return new SatelliteCapabilities[size];
+                }
+            };
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SupportedRadioTechnology:");
+        if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+            for (int technology : mSupportedRadioTechnologies) {
+                sb.append(technology);
+                sb.append(",");
+            }
+        } else {
+            sb.append("none,");
+        }
+
+        sb.append("SupportedFeatures:");
+        if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+            for (int feature : mSupportedFeatures) {
+                sb.append(feature);
+                sb.append(",");
+            }
+        } else {
+            sb.append("none,");
+        }
+
+        sb.append("isAlwaysOn:");
+        sb.append(mIsAlwaysOn);
+        sb.append(",");
+
+        sb.append("needsPointingToSatellite:");
+        sb.append(mNeedsPointingToSatellite);
+        sb.append(",");
+
+        sb.append("needsSeparateSimProfile:");
+        sb.append(mNeedsSeparateSimProfile);
+        return sb.toString();
+    }
+
+    @NonNull
+    public Set<Integer> getSupportedRadioTechnologies() {
+        return mSupportedRadioTechnologies;
+    }
+
+    public boolean isAlwaysOn() {
+        return mIsAlwaysOn;
+    }
+
+    /** Get function for mNeedsPointingToSatellite */
+    public boolean needsPointingToSatellite() {
+        return mNeedsPointingToSatellite;
+    }
+
+    @NonNull
+    public Set<Integer> getSupportedFeatures() {
+        return mSupportedFeatures;
+    }
+
+    /** Get function for mNeedsSeparateSimProfile */
+    public boolean needsSeparateSimProfile() {
+        return mNeedsSeparateSimProfile;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSupportedRadioTechnologies = new HashSet<>();
+        int numSupportedRadioTechnologies = in.readInt();
+        if (numSupportedRadioTechnologies > 0) {
+            for (int i = 0; i < numSupportedRadioTechnologies; i++) {
+                mSupportedRadioTechnologies.add(in.readInt());
+            }
+        }
+
+        mIsAlwaysOn = in.readBoolean();
+        mNeedsPointingToSatellite = in.readBoolean();
+
+        mSupportedFeatures = new HashSet<>();
+        int numSupportedFeatures = in.readInt();
+        if (numSupportedFeatures > 0) {
+            for (int i = 0; i < numSupportedFeatures; i++) {
+                mSupportedFeatures.add(in.readInt());
+            }
+        }
+
+        mNeedsSeparateSimProfile = in.readBoolean();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
new file mode 100644
index 0000000..d3964a8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * @hide
+ */
+public class SatelliteImplBase {
+    private static final String TAG = "SatelliteImplBase";
+
+    /**@hide*/
+    @IntDef(
+            prefix = "TECHNOLOGY_",
+            value = {
+                    TECHNOLOGY_NB_IOT_NTN,
+                    TECHNOLOGY_NR_NTN,
+                    TECHNOLOGY_EMTC_NTN,
+                    TECHNOLOGY_PROPRIETARY
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NTRadioTechnology {}
+
+    /** 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology */
+    public static final int TECHNOLOGY_NB_IOT_NTN =
+            android.hardware.radio.satellite.NTRadioTechnology.NB_IOT_NTN;
+    /** 3GPP 5G NR over Non-Terrestrial-Networks technology */
+    public static final int TECHNOLOGY_NR_NTN =
+            android.hardware.radio.satellite.NTRadioTechnology.NR_NTN;
+    /** 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology */
+    public static final int TECHNOLOGY_EMTC_NTN =
+            android.hardware.radio.satellite.NTRadioTechnology.EMTC_NTN;
+    /** Proprietary technology like Iridium or Bullitt */
+    public static final int TECHNOLOGY_PROPRIETARY =
+            android.hardware.radio.satellite.NTRadioTechnology.PROPRIETARY;
+
+    /**@hide*/
+    @IntDef(
+            prefix = "FEATURE_",
+            value = {
+                    FEATURE_SOS_SMS,
+                    FEATURE_EMERGENCY_SMS,
+                    FEATURE_SMS,
+                    FEATURE_LOCATION_SHARING,
+                    FEATURE_UNKNOWN
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Feature {}
+
+    /** Able to send and receive SMS messages to/from SOS numbers like call/service centers */
+    public static final int FEATURE_SOS_SMS =
+            android.hardware.radio.satellite.SatelliteFeature.SOS_SMS;
+    /** Able to send and receive SMS messages to/from emergency numbers like 911 */
+    public static final int FEATURE_EMERGENCY_SMS =
+            android.hardware.radio.satellite.SatelliteFeature.EMERGENCY_SMS;
+    /** Able to send and receive SMS messages to/from any allowed contacts */
+    public static final int FEATURE_SMS = android.hardware.radio.satellite.SatelliteFeature.SMS;
+    /** Able to send device location to allowed contacts */
+    public static final int FEATURE_LOCATION_SHARING =
+            android.hardware.radio.satellite.SatelliteFeature.LOCATION_SHARING;
+    /** This feature is not defined in satellite HAL APIs */
+    public static final int FEATURE_UNKNOWN = 0xFFFF;
+
+    /**@hide*/
+    @IntDef(
+            prefix = "MODE_",
+            value = {
+                    MODE_POWERED_OFF,
+                    MODE_OUT_OF_SERVICE_NOT_SEARCHING,
+                    MODE_OUT_OF_SERVICE_SEARCHING,
+                    MODE_ACQUIRED,
+                    MODE_MESSAGE_TRANSFERRING
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {}
+
+    /** Satellite modem is powered off */
+    public static final int MODE_POWERED_OFF =
+            android.hardware.radio.satellite.SatelliteMode.POWERED_OFF;
+    /** Satellite modem is in out of service state and not searching for satellite signal */
+    public static final int MODE_OUT_OF_SERVICE_NOT_SEARCHING =
+            android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_NOT_SEARCHING;
+    /** Satellite modem is in out of service state and searching for satellite signal */
+    public static final int MODE_OUT_OF_SERVICE_SEARCHING =
+            android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_SEARCHING;
+    /** Satellite modem has found satellite signal and gets connected to the satellite network */
+    public static final int MODE_ACQUIRED = android.hardware.radio.satellite.SatelliteMode.ACQUIRED;
+    /** Satellite modem is sending and/or receiving messages */
+    public static final int MODE_MESSAGE_TRANSFERRING =
+            android.hardware.radio.satellite.SatelliteMode.MESSAGE_TRANSFERRING;
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a9af199..6e56963 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,22 @@
     int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
     int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+    int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+    int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
+    int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
+    int RIL_REQUEST_SET_SATELLITE_POWER = 246;
+    int RIL_REQUEST_GET_SATELLITE_POWER = 247;
+    int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
+    int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
+    int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
+    int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
+    int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
+    int RIL_REQUEST_GET_SATELLITE_MODE = 253;
+    int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
+    int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
+    int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
+    int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
+    int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,6 +623,13 @@
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
     int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
+    int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
+    int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
+    int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
+    int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
+    int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
+    int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
+    int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 487a0c3..462f91b 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -43,7 +43,7 @@
     <!-- ActivityOptions.makeCustomTaskAnimation() -->
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true">
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
         <uses-library android:name="android.test.runner"/>
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
     </application>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 8fe6aac..18792a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -103,6 +103,54 @@
     }
 
     /**
+     * Minimizes the PIP window my using the pinch in gesture.
+     *
+     * @param percent The percentage by which to decrease the pip window size.
+     * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
+     */
+    fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
+        // the percentage must be between 0.0f and 1.0f
+        if (percent <= 0.0f || percent > 1.0f) {
+            throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
+        }
+
+        val windowRect = getWindowRect(wmHelper)
+
+        // first pointer's initial x coordinate is halfway between the left edge and the center
+        val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+        // second pointer's initial x coordinate is halfway between the right edge and the center
+        val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+
+        // decrease by the distance specified through the percentage
+        val distDecrease = windowRect.width * percent
+
+        // get the final x-coordinates and make sure they are not passing the center of the window
+        val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
+        val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
+
+        // y-coordinate is the same throughout this animation
+        val yCoord = windowRect.centerY().toFloat()
+
+        var adjustedSteps = MIN_STEPS_TO_ANIMATE
+
+        // if distance per step is at least 1, then we can use the number of steps requested
+        if (distDecrease.toInt() / (steps * 2) >= 1) {
+            adjustedSteps = steps
+        }
+
+        // if the distance per step is less than 1, carry out the animation in two steps
+        gestureHelper.pinch(
+            Tuple(initLeftX, yCoord),
+            Tuple(initRightX, yCoord),
+            Tuple(finalLeftX, yCoord),
+            Tuple(finalRightX, yCoord),
+            adjustedSteps
+        )
+
+        waitForPipWindowToMinimizeFrom(wmHelper, Region.from(windowRect))
+    }
+
+    /**
      * Launches the app through an intent instead of interacting with the launcher and waits until
      * the app window is in PIP mode
      */
@@ -238,6 +286,24 @@
             .waitForAndVerify()
     }
 
+    private fun waitForPipWindowToMinimizeFrom(
+        wmHelper: WindowManagerStateHelper,
+        windowRect: Region
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("pipWindowMinimized") {
+                val pipAppWindow =
+                        it.wmState.visibleWindows.firstOrNull { window ->
+                            this.windowMatchesAnyOf(window)
+                        }
+                                ?: return@add false
+                val pipRegion = pipAppWindow.frameRegion
+                return@add windowRect.coversMoreThan(pipRegion)
+            }
+            .waitForAndVerify()
+    }
+
     companion object {
         private const val TAG = "PipAppHelper"
         private const val ENTER_PIP_BUTTON_ID = "enter_pip"
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index ddcc811..d075b5f 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.hardware.input.HostUsiVersion;
 import android.os.Parcel;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,6 +58,8 @@
         assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag());
         assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType());
         assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
+        assertEquals(device.getHostUsiVersion(), outDevice.getHostUsiVersion());
+        assertEquals(device.getAssociatedDisplayId(), outDevice.getAssociatedDisplayId());
 
         KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
         KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap();
@@ -88,7 +91,7 @@
                 .setHasBattery(true)
                 .setKeyboardLanguageTag("en-US")
                 .setKeyboardLayoutType("qwerty")
-                .setSupportsUsi(true);
+                .setUsiVersion(new HostUsiVersion(2, 0));
 
         for (int i = 0; i < 30; i++) {
             deviceBuilder.addMotionRange(
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index bf6ece1..e704cc2 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -17,8 +17,10 @@
 package com.android.internal.app;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.os.LocaleList;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
@@ -27,23 +29,21 @@
 
 import com.android.internal.app.LocaleStore.LocaleInfo;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IllformedLocaleException;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
-/**
- * Unit tests for the {@link LocaleStore}.
- */
+/** Unit tests for the {@link LocaleStore}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class LocaleStoreTest {
-    @Before
-    public void setUp() {
-    }
-
     @Test
     public void testTransformImeLanguageTagToLocaleInfo() {
         List<InputMethodSubtype> list = List.of(
@@ -60,4 +60,99 @@
             assertTrue(expectedLanguageTag.contains(info.getId()));
         }
     }
+
+    @Test
+    public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(
+                        LocaleList.getEmptyLocaleList(), supportedLocale);
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void convertExplicitLocales_hasEmptyLocale_receiveException() {
+        Locale[] locales = {Locale.forLanguageTag(""), Locale.forLanguageTag("en-US")};
+        LocaleList localelist = new LocaleList(locales);
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        boolean isReceiveException = false;
+        try {
+            LocaleStore.convertExplicitLocales(localelist, supportedLocale);
+        } catch (IllformedLocaleException e) {
+            isReceiveException = true;
+        }
+
+        assertTrue(isReceiveException);
+    }
+
+    @Test
+    public void convertExplicitLocales_hasSameLocale_returnNonSameLocales() {
+        LocaleList locales = LocaleList.forLanguageTags("en-US,en-US");
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+        // Only has "en" and "en-US".
+        assertTrue(result.size() == 2);
+    }
+
+    @Test
+    public void convertExplicitLocales_hasEnUs_resultHasParentEn() {
+        LocaleList locales = LocaleList.forLanguageTags("en-US,ja-JP");
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+        assertEquals(result.get("en").getId(), "en");
+    }
+
+    @Test
+    public void convertExplicitLocales_hasZhTw_resultZhHantTw() {
+        LocaleList locales = LocaleList.forLanguageTags("zh-TW,en-US,en");
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+        assertEquals("zh-Hant-TW", result.get("zh-Hant-TW").getId());
+    }
+
+    @Test
+    public void convertExplicitLocales_nonRegularFormat_resultEmptyContry() {
+        LocaleList locales = LocaleList.forLanguageTags("de-1996,de-1901");
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+        assertEquals("de-1996", result.get("de-1996").getId());
+        assertTrue(result.get("de-1996").getLocale().getCountry().isEmpty());
+    }
+
+    @Test
+    public void convertExplicitLocales_differentEnFormat() {
+        LocaleList locales = LocaleList.forLanguageTags("en-Latn-US,en-US,en");
+        Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+        HashMap<String, LocaleInfo> result =
+                LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+        assertEquals("en", result.get("en").getId());
+        assertEquals("en-US", result.get("en-US").getId());
+        assertNull(result.get("en-Latn-US"));
+    }
+
+    private ArrayList<LocaleInfo> getFakeSupportedLocales() {
+        String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+        ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
+        for (String localeTag : locales) {
+            supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
+        }
+        return supportedLocales;
+    }
 }
diff --git a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
index 79a2f1f..157d197 100644
--- a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
+++ b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
@@ -89,6 +89,7 @@
             for (int i = 0; i < mIdsToRollback.size(); i++) {
                 Intent intent = new Intent(ACTION_NAME);
                 intent.putExtra(ROLLBACK_ID_EXTRA, mIdsToRollback.get(i));
+                intent.setPackage(getApplicationContext().getPackageName());
                 PendingIntent pendingIntent = PendingIntent.getBroadcast(
                         getApplicationContext(), 0, intent, FLAG_MUTABLE);
                 mRollbackManager.commitRollback(mIdsToRollback.get(i),