Merge "InputManagerService - call PermissionEnforcer helper on methods annotated with @EnforcePermission"
diff --git a/core/api/current.txt b/core/api/current.txt
index 53dbef8..b64afea 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7669,6 +7669,7 @@
     method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
     method public void wipeData(int);
     method public void wipeData(int, @NonNull CharSequence);
+    method public void wipeDevice(int);
     field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
     field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -16597,6 +16598,7 @@
     field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190
     field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258
     field public static final int FONT_WEIGHT_THIN = 100; // 0x64
+    field public static final int FONT_WEIGHT_UNSPECIFIED = -1; // 0xffffffff
   }
 
   public final class FontVariationAxis {
@@ -40389,6 +40391,7 @@
     field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+    field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
     field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
     field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
     field public static final String EXTRA_SUGGESTED_PHONE_ACCOUNTS = "android.telecom.extra.SUGGESTED_PHONE_ACCOUNTS";
@@ -44117,7 +44120,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
-    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14; // 0xe
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b5b87c1..db1e4e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12120,6 +12120,7 @@
     method public static int getMaxScore();
     method @Nullable public android.media.MediaSyncEvent getMediaSyncEvent();
     method public int getPersonalizedScore();
+    method public int getProximity();
     method public int getScore();
     method public boolean isHotwordDetectionPersonalized();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12133,6 +12134,9 @@
     field public static final int CONFIDENCE_LEVEL_VERY_HIGH = 6; // 0x6
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordDetectedResult> CREATOR;
     field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+    field public static final int PROXIMITY_FAR = 2; // 0x2
+    field public static final int PROXIMITY_NEAR = 1; // 0x1
+    field public static final int PROXIMITY_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class HotwordDetectedResult.Builder {
@@ -12469,6 +12473,8 @@
     field public static final int CS_BOUND = 6; // 0x6
     field public static final int DIRECT_TO_VM_FINISHED = 103; // 0x67
     field public static final int DIRECT_TO_VM_INITIATED = 102; // 0x66
+    field public static final int DND_CHECK_COMPLETED = 110; // 0x6e
+    field public static final int DND_CHECK_INITIATED = 109; // 0x6d
     field public static final int FILTERING_COMPLETED = 107; // 0x6b
     field public static final int FILTERING_INITIATED = 106; // 0x6a
     field public static final int FILTERING_TIMED_OUT = 108; // 0x6c
@@ -12508,6 +12514,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.EventTiming> CREATOR;
     field public static final int DIRECT_TO_VM_FINISHED_TIMING = 8; // 0x8
     field public static final int DISCONNECT_TIMING = 2; // 0x2
+    field public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12; // 0xc
     field public static final int FILTERING_COMPLETED_TIMING = 10; // 0xa
     field public static final int FILTERING_TIMED_OUT_TIMING = 11; // 0xb
     field public static final int HOLD_TIMING = 3; // 0x3
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d8419a8..535f6ce 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -758,6 +758,7 @@
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+    field public static final String ATTENTION_SERVICE = "attention";
     field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
     field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
     field public static final String DREAM_SERVICE = "dream";
@@ -2499,6 +2500,10 @@
     method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
   }
 
+  public abstract class HotwordDetectionService extends android.app.Service {
+    field public static final boolean ENABLE_PROXIMITY_RESULT = true;
+  }
+
   public final class VisibleActivityInfo implements android.os.Parcelable {
     ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
   }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index dc325ff..860bfc5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2403,6 +2403,9 @@
                 "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
     };
 
+    // The number of longs needed to form a full bitmask of app ops
+    private static final int BITMASK_LEN = ((_NUM_OP - 1) / Long.SIZE) + 1;
+
     /**
      * @hide
      */
@@ -2437,8 +2440,8 @@
      * @see #getNotedOpCollectionMode
      * @see #collectNotedOpSync
      */
-    private static final ThreadLocal<ArrayMap<String, long[]>> sAppOpsNotedInThisBinderTransaction =
-            new ThreadLocal<>();
+    private static final ThreadLocal<ArrayMap<String, BitSet>>
+            sAppOpsNotedInThisBinderTransaction = new ThreadLocal<>();
 
     static {
         if (sAppOpInfos.length != _NUM_OP) {
@@ -8684,10 +8687,10 @@
      */
     public static class PausedNotedAppOpsCollection {
         final int mUid;
-        final @Nullable ArrayMap<String, long[]> mCollectedNotedAppOps;
+        final @Nullable ArrayMap<String, BitSet> mCollectedNotedAppOps;
 
         PausedNotedAppOpsCollection(int uid, @Nullable ArrayMap<String,
-                long[]> collectedNotedAppOps) {
+                BitSet> collectedNotedAppOps) {
             mUid = uid;
             mCollectedNotedAppOps = collectedNotedAppOps;
         }
@@ -8705,7 +8708,7 @@
     public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
         Integer previousUid = sBinderThreadCallingUid.get();
         if (previousUid != null) {
-            ArrayMap<String, long[]> previousCollectedNotedAppOps =
+            ArrayMap<String, BitSet> previousCollectedNotedAppOps =
                     sAppOpsNotedInThisBinderTransaction.get();
 
             sBinderThreadCallingUid.remove();
@@ -8779,23 +8782,19 @@
         // We are inside of a two-way binder call. Delivered to caller via
         // {@link #prefixParcelWithAppOpsIfNeeded}
         int op = sOpStrToOp.get(syncOp.getOp());
-        ArrayMap<String, long[]> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
+        ArrayMap<String, BitSet> appOpsNoted = sAppOpsNotedInThisBinderTransaction.get();
         if (appOpsNoted == null) {
             appOpsNoted = new ArrayMap<>(1);
             sAppOpsNotedInThisBinderTransaction.set(appOpsNoted);
         }
 
-        long[] appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
+        BitSet appOpsNotedForAttribution = appOpsNoted.get(syncOp.getAttributionTag());
         if (appOpsNotedForAttribution == null) {
-            appOpsNotedForAttribution = new long[2];
+            appOpsNotedForAttribution = new BitSet(_NUM_OP);
             appOpsNoted.put(syncOp.getAttributionTag(), appOpsNotedForAttribution);
         }
 
-        if (op < 64) {
-            appOpsNotedForAttribution[0] |= 1L << op;
-        } else {
-            appOpsNotedForAttribution[1] |= 1L << (op - 64);
-        }
+        appOpsNotedForAttribution.set(op);
     }
 
     /** @hide */
@@ -8869,7 +8868,7 @@
      */
     // TODO (b/186872903) Refactor how sync noted ops are propagated.
     public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
-        ArrayMap<String, long[]> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+        ArrayMap<String, BitSet> notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
         if (notedAppOps == null) {
             return;
         }
@@ -8881,8 +8880,15 @@
 
         for (int i = 0; i < numAttributionWithNotesAppOps; i++) {
             p.writeString(notedAppOps.keyAt(i));
-            p.writeLong(notedAppOps.valueAt(i)[0]);
-            p.writeLong(notedAppOps.valueAt(i)[1]);
+            // Bitmask's toLongArray will truncate the array, if upper bits arent used
+            long[] notedOpsMask = notedAppOps.valueAt(i).toLongArray();
+            for (int j = 0; j < BITMASK_LEN; j++) {
+                if (j < notedOpsMask.length) {
+                    p.writeLong(notedOpsMask[j]);
+                } else {
+                    p.writeLong(0);
+                }
+            }
         }
     }
 
@@ -8901,12 +8907,13 @@
 
         for (int i = 0; i < numAttributionsWithNotedAppOps; i++) {
             String attributionTag = p.readString();
-            long[] rawNotedAppOps = new long[2];
-            rawNotedAppOps[0] = p.readLong();
-            rawNotedAppOps[1] = p.readLong();
+            long[] rawNotedAppOps = new long[BITMASK_LEN];
+            for (int j = 0; j < rawNotedAppOps.length; j++) {
+                rawNotedAppOps[j] = p.readLong();
+            }
+            BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
 
-            if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
-                BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+            if (!notedAppOps.isEmpty()) {
 
                 synchronized (sLock) {
                     for (int code = notedAppOps.nextSetBit(0); code != -1;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f4cee5a..6fedb41 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -171,6 +171,7 @@
     private final boolean mParentInstance;
     private final DevicePolicyResourcesManager mResourcesManager;
 
+
     /** @hide */
     public DevicePolicyManager(Context context, IDevicePolicyManager service) {
         this(context, service, false);
@@ -6207,46 +6208,46 @@
     public static final int WIPE_SILENTLY = 0x0008;
 
     /**
-     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
-     * other users will remain unaffected. Calling from the primary user will cause the device to
-     * reboot, erasing all device data - including all the secondary users and their data - while
-     * booting up.
-     * <p>
-     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
-     * be able to call this method; if it has not, a security exception will be thrown.
-     *
-     * If the caller is a profile owner of an organization-owned managed profile, it may
-     * additionally call this method on the parent instance.
-     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
-     * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the managed profile and all policies set by the profile owner.
+     * See {@link #wipeData(int, CharSequence)}
      *
      * @param flags Bit mask of additional options: currently supported flags are
-     *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
-     *            {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
-     * @throws SecurityException if the calling application does not own an active administrator
-     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
-     *            {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+     * @throws SecurityException     if the calling application does not own an active
+     *                               administrator
+     *                               that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is
+     *                               not granted the
+     *                               {@link android.Manifest.permission#MASTER_CLEAR} permission.
+     * @throws IllegalStateException if called on last full-user or system-user
+     * @see #wipeDevice(int)
+     * @see #wipeData(int, CharSequence)
      */
     public void wipeData(int flags) {
-        wipeDataInternal(flags, "");
+        wipeDataInternal(flags,
+                /* wipeReasonForUser= */ "",
+                /* factoryReset= */ false);
     }
 
     /**
-     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
-     * other users will remain unaffected, the provided reason for wiping data can be shown to
-     * user. Calling from the primary user will cause the device to reboot, erasing all device data
-     * - including all the secondary users and their data - while booting up. In this case, we don't
-     * show the reason to the user since the device would be factory reset.
-     * <p>
-     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
-     * be able to call this method; if it has not, a security exception will be thrown.
+     * Ask that all user data be wiped.
      *
-     * If the caller is a profile owner of an organization-owned managed profile, it may
-     * additionally call this method on the parent instance.
-     * Calling this method on the parent {@link DevicePolicyManager} instance would wipe the
-     * entire device, while calling it on the current profile instance would relinquish the device
-     * for personal use, removing the managed profile and all policies set by the profile owner.
+     * <p>
+     * If called as a secondary user or managed profile, the user itself and its associated user
+     * data will be wiped. In particular, If the caller is a profile owner of an
+     * organization-owned managed profile, calling this method will relinquish the device for
+     * personal use, removing the managed profile and all policies set by the profile owner.
+     * </p>
+     *
+     * <p>
+     * Calling this method from the primary user will only work if the calling app is targeting
+     * Android 13 or below, in which case it will cause the device to reboot, erasing all device
+     * data - including all the secondary users and their data - while booting up. If an app
+     * targeting Android 13+ is calling this method from the primary user or last full user,
+     * {@link IllegalStateException} will be thrown.
+     * </p>
+     *
+     * If an app wants to wipe the entire device irrespective of which user they are from, they
+     * should use {@link #wipeDevice} instead.
      *
      * @param flags Bit mask of additional options: currently supported flags are
      *            {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
@@ -6254,30 +6255,61 @@
      * @param reason a string that contains the reason for wiping data, which can be
      *            presented to the user.
      * @throws SecurityException if the calling application does not own an active administrator
-     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} or is not granted the
+     *            that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not granted the
      *            {@link android.Manifest.permission#MASTER_CLEAR} permission.
      * @throws IllegalArgumentException if the input reason string is null or empty, or if
      *            {@link #WIPE_SILENTLY} is set.
+     * @throws IllegalStateException if called on last full-user or system-user
+     * @see #wipeDevice(int)
+     * @see #wipeData(int)
      */
     public void wipeData(int flags, @NonNull CharSequence reason) {
         Objects.requireNonNull(reason, "reason string is null");
         Preconditions.checkStringNotEmpty(reason, "reason string is empty");
         Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
-        wipeDataInternal(flags, reason.toString());
+        wipeDataInternal(flags, reason.toString(), /* factoryReset= */ false);
     }
 
     /**
-     * Internal function for both {@link #wipeData(int)} and
-     * {@link #wipeData(int, CharSequence)} to call.
+     * Ask that the device be wiped and factory reset.
      *
+     * <p>
+     * The calling Device Owner or Organization Owned Profile Owner must have requested
+     * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call this method; if it has
+     * not, a security exception will be thrown.
+     *
+     * @param flags Bit mask of additional options: currently supported flags are
+     *              {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+     *              {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
+     * @throws SecurityException if the calling application does not own an active administrator
+     *                           that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} and is not
+     *                           granted the {@link android.Manifest.permission#MASTER_CLEAR}
+     *                           permission.
      * @see #wipeData(int)
      * @see #wipeData(int, CharSequence)
-     * @hide
      */
-    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+    // TODO(b/255323293) Add host-side tests
+    public void wipeDevice(int flags) {
+        wipeDataInternal(flags,
+                /* wipeReasonForUser= */ "",
+                /* factoryReset= */ true);
+    }
+
+    /**
+     * Internal function for {@link #wipeData(int)}, {@link #wipeData(int, CharSequence)}
+     * and {@link #wipeDevice(int)} to call.
+     *
+     * @hide
+     * @see #wipeData(int)
+     * @see #wipeData(int, CharSequence)
+     * @see #wipeDevice(int)
+     */
+    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser,
+            boolean factoryReset) {
         if (mService != null) {
             try {
-                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance);
+                mService.wipeDataWithReason(flags, wipeReasonForUser, mParentInstance,
+                        factoryReset);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8642,7 +8674,7 @@
     public void reportFailedPasswordAttempt(int userHandle) {
         if (mService != null) {
             try {
-                mService.reportFailedPasswordAttempt(userHandle);
+                mService.reportFailedPasswordAttempt(userHandle, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 75bfc25..6c27dd7 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -117,7 +117,10 @@
 
     void lockNow(int flags, boolean parent);
 
-    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+    /**
+    * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise
+    **/
+    void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent, boolean factoryReset);
 
     void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
     FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
@@ -161,7 +164,7 @@
     boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
 
     void reportPasswordChanged(in PasswordMetrics metrics, int userId);
-    void reportFailedPasswordAttempt(int userHandle);
+    void reportFailedPasswordAttempt(int userHandle, boolean parent);
     void reportSuccessfulPasswordAttempt(int userHandle);
     void reportFailedBiometricAttempt(int userHandle);
     void reportSuccessfulBiometricAttempt(int userHandle);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1df0fa8..ae1f689 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4917,6 +4917,7 @@
      * @see android.server.attention.AttentionManagerService
      * @hide
      */
+    @TestApi
     public static final String ATTENTION_SERVICE = "attention";
 
     /**
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 0d3c8db..fd35378 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -45,6 +45,7 @@
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
     private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
     private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy";
+    private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
@@ -52,6 +53,7 @@
             INDEX_START_WITH_PARENT,
             INDEX_SHOW_IN_SETTINGS,
             INDEX_INHERIT_DEVICE_POLICY,
+            INDEX_USE_PARENTS_CONTACTS,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -60,6 +62,7 @@
     private static final int INDEX_START_WITH_PARENT = 1;
     private static final int INDEX_SHOW_IN_SETTINGS = 2;
     private static final int INDEX_INHERIT_DEVICE_POLICY = 3;
+    private static final int INDEX_USE_PARENTS_CONTACTS = 4;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -200,6 +203,7 @@
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
             setShowInSettings(orig.getShowInSettings());
+            setUseParentsContacts(orig.getUseParentsContacts());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -317,6 +321,39 @@
     }
     private @InheritDevicePolicy int mInheritDevicePolicy;
 
+    /**
+     * Returns whether the current user must use parent user's contacts. If true, writes to the
+     * ContactsProvider corresponding to the current user will be disabled and reads will be
+     * redirected to the parent.
+     *
+     * This only applies to users that have parents (i.e. profiles) and is used to ensure
+     * they can access contacts from the parent profile. This will be generally inapplicable for
+     * non-profile users.
+     *
+     * Please note that in case of the clone profiles, only the allow-listed apps would be allowed
+     * to access contacts across profiles and other apps will not see any contacts.
+     * TODO(b/256126819) Add link to the method returning apps allow-listed for app-cloning
+     *
+     * @return whether contacts access from an associated profile is enabled for the user
+     * @hide
+     */
+    public boolean getUseParentsContacts() {
+        if (isPresent(INDEX_USE_PARENTS_CONTACTS)) return mUseParentsContacts;
+        if (mDefaultProperties != null) return mDefaultProperties.mUseParentsContacts;
+        throw new SecurityException("You don't have permission to query useParentsContacts");
+    }
+    /** @hide */
+    public void setUseParentsContacts(boolean val) {
+        this.mUseParentsContacts = val;
+        setPresent(INDEX_USE_PARENTS_CONTACTS);
+    }
+    /**
+     * Indicates whether the current user should use parent user's contacts.
+     * If this property is set true, the user will be blocked from storing any contacts in its
+     * own contacts database and will serve all read contacts calls through the parent's contacts.
+     */
+    private boolean mUseParentsContacts;
+
     @Override
     public String toString() {
         // Please print in increasing order of PropertyIndex.
@@ -326,6 +363,7 @@
                 + ", mStartWithParent=" + getStartWithParent()
                 + ", mShowInSettings=" + getShowInSettings()
                 + ", mInheritDevicePolicy=" + getInheritDevicePolicy()
+                + ", mUseParentsContacts=" + getUseParentsContacts()
                 + "}";
     }
 
@@ -341,6 +379,7 @@
         pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
         pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
         pw.println(prefix + "    mInheritDevicePolicy=" + getInheritDevicePolicy());
+        pw.println(prefix + "    mUseParentsContacts=" + getUseParentsContacts());
     }
 
     /**
@@ -386,6 +425,9 @@
                 case ATTR_INHERIT_DEVICE_POLICY:
                     setInheritDevicePolicy(parser.getAttributeInt(i));
                     break;
+                case ATTR_USE_PARENTS_CONTACTS:
+                    setUseParentsContacts(parser.getAttributeBoolean(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -416,6 +458,10 @@
             serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY,
                     mInheritDevicePolicy);
         }
+        if (isPresent(INDEX_USE_PARENTS_CONTACTS)) {
+            serializer.attributeBoolean(null, ATTR_USE_PARENTS_CONTACTS,
+                    mUseParentsContacts);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -426,6 +472,7 @@
         dest.writeBoolean(mStartWithParent);
         dest.writeInt(mShowInSettings);
         dest.writeInt(mInheritDevicePolicy);
+        dest.writeBoolean(mUseParentsContacts);
     }
 
     /**
@@ -440,6 +487,7 @@
         mStartWithParent = source.readBoolean();
         mShowInSettings = source.readInt();
         mInheritDevicePolicy = source.readInt();
+        mUseParentsContacts = source.readBoolean();
     }
 
     @Override
@@ -468,6 +516,7 @@
         private boolean mStartWithParent = false;
         private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
         private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO;
+        private boolean mUseParentsContacts = false;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -492,13 +541,19 @@
             return this;
         }
 
+        public Builder setUseParentsContacts(boolean useParentsContacts) {
+            mUseParentsContacts = useParentsContacts;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
                     mShowInLauncher,
                     mStartWithParent,
                     mShowInSettings,
-                    mInheritDevicePolicy);
+                    mInheritDevicePolicy,
+                    mUseParentsContacts);
         }
     } // end Builder
 
@@ -507,12 +562,14 @@
             @ShowInLauncher int showInLauncher,
             boolean startWithParent,
             @ShowInSettings int showInSettings,
-            @InheritDevicePolicy int inheritDevicePolicy) {
+            @InheritDevicePolicy int inheritDevicePolicy,
+            boolean useParentsContacts) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
         setShowInSettings(showInSettings);
         setInheritDevicePolicy(inheritDevicePolicy);
+        setUseParentsContacts(useParentsContacts);
     }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index d23fb36..d55367f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 import android.view.InputChannel;
 import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
@@ -93,9 +94,10 @@
     final int mTargetSdkVersion;
 
     /**
-     * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
-     * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been
-     * called or not, mainly to avoid unnecessary blocking operations.
+     * This is not {@code null} only between {@link #bindInput(InputBinding)} and
+     * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if
+     * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary
+     * blocking operations.
      *
      * <p>This field must be set and cleared only from the binder thread(s), where the system
      * guarantees that {@link #bindInput(InputBinding)},
@@ -219,18 +221,26 @@
                 return;
             case DO_SHOW_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
+                final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.showSoftInputWithToken(
-                            msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+                            msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
             }
             case DO_HIDE_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
+                final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
                 if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+                    ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                     inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
-                            (IBinder) args.arg1);
+                            (IBinder) args.arg1, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                 }
                 args.recycle();
                 return;
@@ -416,16 +426,20 @@
 
     @BinderThread
     @Override
-    public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
-                flags, showInputToken, resultReceiver));
+    public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
+                flags, showInputToken, resultReceiver, statsToken));
     }
 
     @BinderThread
     @Override
-    public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT,
-                flags, hideInputToken, resultReceiver));
+    public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
+                flags, hideInputToken, resultReceiver, statsToken));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index d902486..bf4fc4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -124,6 +124,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
 import android.view.inputmethod.InputBinding;
@@ -669,6 +670,10 @@
      */
     private IBinder mCurHideInputToken;
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mCurStatsToken;
+
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
         onComputeInsets(mTmpInsets);
         if (!mViewsCreated) {
@@ -870,10 +875,12 @@
         @MainThread
         @Override
         public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder hideInputToken) {
+                IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
             mSystemCallingHideSoftInput = true;
             mCurHideInputToken = hideInputToken;
+            mCurStatsToken = statsToken;
             hideSoftInput(flags, resultReceiver);
+            mCurStatsToken = null;
             mCurHideInputToken = null;
             mSystemCallingHideSoftInput = false;
         }
@@ -884,6 +891,7 @@
         @MainThread
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+            ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
                     && !mSystemCallingHideSoftInput) {
@@ -918,12 +926,17 @@
         @MainThread
         @Override
         public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder showInputToken) {
+                IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
-            showSoftInput(flags, resultReceiver);
-            mCurShowInputToken = null;
-            mSystemCallingShowSoftInput = false;
+            mCurStatsToken = statsToken;
+            try {
+                showSoftInput(flags, resultReceiver);
+            } finally {
+                mCurStatsToken = null;
+                mCurShowInputToken = null;
+                mSystemCallingShowSoftInput = false;
+            }
         }
 
         /**
@@ -932,6 +945,7 @@
         @MainThread
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+            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
@@ -947,7 +961,12 @@
                     null /* icProto */);
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
+                ImeTracker.get().onProgress(mCurStatsToken,
+                        ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
                 showWindow(true);
+            } else {
+                ImeTracker.get().onFailed(mCurStatsToken,
+                        ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
             }
             setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
 
@@ -2923,8 +2942,10 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
+        ImeTracker.get().onProgress(mCurStatsToken,
+                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
-                ? mCurShowInputToken : mCurHideInputToken, setVisible);
+                ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
     }
 
     private void finishViews(boolean finishingInput) {
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index d5c3de1..b478a379 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -84,7 +84,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -1314,31 +1313,31 @@
     private static long toBytes(long value, String unit) {
         unit = unit.toUpperCase();
 
-        if (List.of("B").contains(unit)) {
+        if ("B".equals(unit)) {
             return value;
         }
 
-        if (List.of("K", "KB").contains(unit)) {
+        if ("K".equals(unit) || "KB".equals(unit)) {
             return DataUnit.KILOBYTES.toBytes(value);
         }
 
-        if (List.of("M", "MB").contains(unit)) {
+        if ("M".equals(unit) || "MB".equals(unit)) {
             return DataUnit.MEGABYTES.toBytes(value);
         }
 
-        if (List.of("G", "GB").contains(unit)) {
+        if ("G".equals(unit) || "GB".equals(unit)) {
             return DataUnit.GIGABYTES.toBytes(value);
         }
 
-        if (List.of("KI", "KIB").contains(unit)) {
+        if ("KI".equals(unit) || "KIB".equals(unit)) {
             return DataUnit.KIBIBYTES.toBytes(value);
         }
 
-        if (List.of("MI", "MIB").contains(unit)) {
+        if ("MI".equals(unit) || "MIB".equals(unit)) {
             return DataUnit.MEBIBYTES.toBytes(value);
         }
 
-        if (List.of("GI", "GIB").contains(unit)) {
+        if ("GI".equals(unit) || "GIB".equals(unit)) {
             return DataUnit.GIBIBYTES.toBytes(value);
         }
 
@@ -1370,7 +1369,7 @@
                 sign = -1;
             }
 
-            fmtSize = fmtSize.replace(first + "", "");
+            fmtSize = fmtSize.substring(1);
         }
 
         int index = 0;
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 6091bf9..bf72b1d 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.vibrator.IVibrator;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Range;
@@ -313,8 +314,14 @@
         private static final float EPSILON = 1e-5f;
 
         public MultiVibratorInfo(VibratorInfo[] vibrators) {
+            // Need to use an extra constructor to share the computation in super initialization.
+            this(vibrators, frequencyProfileIntersection(vibrators));
+        }
+
+        private MultiVibratorInfo(VibratorInfo[] vibrators,
+                VibratorInfo.FrequencyProfile mergedProfile) {
             super(/* id= */ -1,
-                    capabilitiesIntersection(vibrators),
+                    capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
                     supportedEffectsIntersection(vibrators),
                     supportedBrakingIntersection(vibrators),
                     supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -323,14 +330,19 @@
                     integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
                     integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
                     floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
-                    frequencyProfileIntersection(vibrators));
+                    mergedProfile);
         }
 
-        private static int capabilitiesIntersection(VibratorInfo[] infos) {
+        private static int capabilitiesIntersection(VibratorInfo[] infos,
+                boolean frequencyProfileIsEmpty) {
             int intersection = ~0;
             for (VibratorInfo info : infos) {
                 intersection &= info.getCapabilities();
             }
+            if (frequencyProfileIsEmpty) {
+                // Revoke frequency control if the merged frequency profile ended up empty.
+                intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+            }
             return intersection;
         }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bda2a35..8aed2ac 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1637,6 +1637,16 @@
     /** @hide */
     public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
 
+    /**
+     * System Property used to override whether users can be created even if their type is disabled
+     * or their limit is reached. Set value to 1 to enable.
+     *
+     * <p>Only used on non-user builds.
+     *
+     * @hide
+     */
+    public static final String DEV_CREATE_OVERRIDE_PROPERTY = "debug.user.creation_override";
+
     private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ad39c0c..52b1adb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10431,11 +10431,11 @@
         public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
 
         /**
-         * The duration of timeout, in milliseconds, to switch from a non-primary user to the
-         * primary user when the device is docked.
+         * The duration of timeout, in milliseconds, to switch from a non-Dock User to the
+         * Dock User when the device is docked.
          * @hide
          */
-        public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+        public static final String TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user";
 
         /**
          * Backup manager behavioral parameters.
@@ -18674,6 +18674,9 @@
     /**
      * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
      * in Settings app on large screen devices.
+     *
+     * Developers should resolve the Intent action before using it.
+     *
      * <p>
      *     Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
      * specify the intent for the activity which will be embedded in Settings app.
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 1d4ac25..98c537a 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -173,7 +173,7 @@
          */
         public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
             if (pendingIntent != null) {
-                Preconditions.checkState(mCredential != null,
+                Preconditions.checkState(mCredential == null,
                         "credential is already set. Cannot set both the pendingIntent "
                                 + "and the credential");
             }
@@ -189,7 +189,7 @@
          */
         public @NonNull Builder setCredential(@Nullable Credential credential) {
             if (credential != null) {
-                Preconditions.checkState(mPendingIntent != null,
+                Preconditions.checkState(mPendingIntent == null,
                         "pendingIntent is already set. Cannot set both the "
                                 + "pendingIntent and the credential");
             }
@@ -215,10 +215,10 @@
          * is set, or if both are set.
          */
         public @NonNull CredentialEntry build() {
-            Preconditions.checkState(mPendingIntent == null && mCredential == null,
-                    "Either pendingIntent or credential must be set");
-            Preconditions.checkState(mPendingIntent != null && mCredential != null,
-                    "Cannot set both the pendingIntent and credential");
+            Preconditions.checkState(((mPendingIntent != null && mCredential == null)
+                            || (mPendingIntent == null && mCredential != null)),
+                    "Either pendingIntent or credential must be set, and both cannot"
+                            + "be set at the same time");
             return new CredentialEntry(mType, mSlice, mPendingIntent,
                     mCredential, mAutoSelectAllowed);
         }
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index 2c7a983..f89ad8e 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -24,7 +24,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -96,6 +95,8 @@
         mLabel = mServiceInfo.loadSafeLabel(
                 mContext.getPackageManager(), 0 /* do not ellipsize */,
                 TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+        Log.i(TAG, "mLabel is : " + mLabel + ", for: " + mServiceInfo.getComponentName()
+                .flattenToString());
         populateProviderCapabilities(context, serviceInfo);
     }
 
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index b1b08f4..6f3e786 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -41,6 +41,18 @@
  * @hide
  */
 public abstract class CredentialProviderService extends Service {
+    /** Extra to be used by provider to populate the credential when ending the activity started
+     * through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
+    public static final String EXTRA_SAVE_CREDENTIAL =
+            "android.service.credentials.extra.SAVE_CREDENTIAL";
+
+    /**
+     * Provider must read the value against this extra to receive the complete create credential
+     * request parameters, when a pending intent is launched.
+     */
+    public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+
     private static final String TAG = "CredProviderService";
 
     public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
@@ -64,7 +76,7 @@
     }
 
     @Override
-    public final @NonNull IBinder onBind(@NonNull Intent intent) {
+    @NonNull public final IBinder onBind(@NonNull Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             return mInterface.asBinder();
         }
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
index e06be44..03ba20e 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -119,9 +119,9 @@
          */
         public @NonNull Builder setGetCredentialOptions(
                 @NonNull List<GetCredentialOption> getCredentialOptions) {
-            Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+            Preconditions.checkCollectionNotEmpty(getCredentialOptions,
                     "getCredentialOptions");
-            Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+            Preconditions.checkCollectionElementsNotNull(getCredentialOptions,
                     "getCredentialOptions");
             mGetCredentialOptions = getCredentialOptions;
             return this;
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index b4f20c9..dee560b 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -31,6 +31,8 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -99,14 +101,30 @@
     private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
 
     /**
-     * The bundle key for proximity value
+     * The bundle key for proximity
      *
      * TODO(b/238896013): Move the proximity logic out of bundle to proper API.
-     *
-     * @hide
      */
-    public static final String EXTRA_PROXIMITY_METERS =
-            "android.service.voice.extra.PROXIMITY_METERS";
+    private static final String EXTRA_PROXIMITY =
+            "android.service.voice.extra.PROXIMITY";
+
+    /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */
+    public static final int PROXIMITY_UNKNOWN = -1;
+
+    /** Proximity value that represents that the object is near. */
+    public static final int PROXIMITY_NEAR = 1;
+
+    /** Proximity value that represents that the object is far. */
+    public static final int PROXIMITY_FAR = 2;
+
+    /** @hide */
+    @IntDef(prefix = {"PROXIMITY"}, value = {
+            PROXIMITY_UNKNOWN,
+            PROXIMITY_NEAR,
+            PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProximityValue {}
 
     /** Confidence level in the trigger outcome. */
     @HotwordConfidenceLevelValue
@@ -220,12 +238,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -348,16 +368,16 @@
             // Remove the proximity key from the bundle before checking the bundle size. The
             // proximity value is added after the privileged module and can avoid the
             // maxBundleSize limitation.
-            if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
-                double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
-                mExtras.remove(EXTRA_PROXIMITY_METERS);
+            if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+                int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+                mExtras.remove(EXTRA_PROXIMITY);
                 // Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
                 // has parcelable size of 4, but the default bundle has parcelable size of 0.
                 if (mExtras.size() > 0) {
                     Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                             getMaxBundleSize(), "extras");
                 }
-                mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+                mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
             } else {
                 Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
                         getMaxBundleSize(), "extras");
@@ -372,6 +392,52 @@
         return List.copyOf(mAudioStreams);
     }
 
+    /**
+     * Adds proximity level, either near or far, that is mapped for the given distance into
+     * the bundle. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond.
+     * This mapping will be excluded from the max bundle size calculation because this mapping is
+     * included after the result is returned from the hotword detector service. The value will not
+     * be included if the proximity was unknown.
+     *
+     * @hide
+     */
+    public void setProximity(double distance) {
+        int proximityLevel = convertToProximityLevel(distance);
+        if (proximityLevel != PROXIMITY_UNKNOWN) {
+            mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+        }
+    }
+
+    /**
+     * Returns proximity level, which can be either of {@link HotwordDetectedResult#PROXIMITY_NEAR}
+     * or {@link HotwordDetectedResult#PROXIMITY_FAR}. If the proximity is unknown, it will
+     * return {@link HotwordDetectedResult#PROXIMITY_UNKNOWN}.
+     */
+    @ProximityValue
+    public int getProximity() {
+        return mExtras.getInt(EXTRA_PROXIMITY, PROXIMITY_UNKNOWN);
+    }
+
+    /**
+     * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+     * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+     * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+     * move to a more centralized place once there are more clients.
+     *
+     * TODO(b/258531144): Move the proximity mapping to a central location
+     */
+    @ProximityValue
+    private int convertToProximityLevel(double distance) {
+        if (distance < 0) {
+            return PROXIMITY_UNKNOWN;
+        } else if (distance <= 3) {
+            return PROXIMITY_NEAR;
+        } else {
+            return PROXIMITY_FAR;
+        }
+    }
+
     @DataClass.Suppress("addAudioStreams")
     abstract static class BaseBuilder {
         /**
@@ -432,7 +498,7 @@
         CONFIDENCE_LEVEL_HIGH,
         CONFIDENCE_LEVEL_VERY_HIGH
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     public @interface ConfidenceLevel {}
 
@@ -463,7 +529,7 @@
         LIMIT_HOTWORD_OFFSET_MAX_VALUE,
         LIMIT_AUDIO_CHANNEL_MAX_VALUE
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     /* package-private */ @interface Limit {}
 
@@ -479,6 +545,30 @@
         }
     }
 
+    /** @hide */
+    @IntDef(prefix = "PROXIMITY_", value = {
+        PROXIMITY_UNKNOWN,
+        PROXIMITY_NEAR,
+        PROXIMITY_FAR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Proximity {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String proximityToString(@Proximity int value) {
+        switch (value) {
+            case PROXIMITY_UNKNOWN:
+                    return "PROXIMITY_UNKNOWN";
+            case PROXIMITY_NEAR:
+                    return "PROXIMITY_NEAR";
+            case PROXIMITY_FAR:
+                    return "PROXIMITY_FAR";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ HotwordDetectedResult(
             @HotwordConfidenceLevelValue int confidenceLevel,
@@ -605,12 +695,14 @@
      * versions of Android.
      *
      * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-     * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-     * will be stored to enable proximity logic. The proximity meters is provided by the system,
-     * on devices that support detecting proximity of nearby users, to help disambiguate which
-     * nearby device should respond. When the proximity is unknown, the proximity value will not
-     * be stored. This mapping will be excluded from the max bundle size calculation because this
-     * mapping is included after the result is returned from the hotword detector service.
+     * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+     * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+     * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+     * proximity. The proximity value is provided by the system, on devices that support detecting
+     * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+     * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+     * from the max bundle size calculation because this mapping is included after the result is
+     * returned from the hotword detector service.
      *
      * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
      * that can be used to communicate with other processes.
@@ -923,12 +1015,14 @@
          * versions of Android.
          *
          * <p>After the trigger happens, a special case of proximity-related extra, with the key of
-         * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
-         * will be stored to enable proximity logic. The proximity meters is provided by the system,
-         * on devices that support detecting proximity of nearby users, to help disambiguate which
-         * nearby device should respond. When the proximity is unknown, the proximity value will not
-         * be stored. This mapping will be excluded from the max bundle size calculation because this
-         * mapping is included after the result is returned from the hotword detector service.
+         * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+         * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+         * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+         * proximity. The proximity value is provided by the system, on devices that support detecting
+         * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+         * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+         * from the max bundle size calculation because this mapping is included after the result is
+         * returned from the hotword detector service.
          *
          * <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
          * that can be used to communicate with other processes.
@@ -1003,10 +1097,10 @@
     }
 
     @DataClass.Generated(
-            time = 1666342044844L,
+            time = 1668385264834L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final  java.lang.String EXTRA_PROXIMITY\npublic static final  int PROXIMITY_UNKNOWN\npublic static final  int PROXIMITY_NEAR\npublic static final  int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index df69cc0..552a793 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -25,6 +25,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
@@ -90,10 +91,10 @@
     /**
      * Feature flag for Attention Service.
      *
-     * TODO(b/247920386): Add TestApi annotation
      * @hide
      */
-    public static final boolean ENABLE_PROXIMITY_RESULT = false;
+    @TestApi
+    public static final boolean ENABLE_PROXIMITY_RESULT = true;
 
     /**
      * Indicates that the updated status is successful.
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 85b7ae9..d61228b 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -149,7 +149,7 @@
         }
 
         mTextFontWeight = a.getInt(com.android.internal.R.styleable
-                .TextAppearance_textFontWeight, -1);
+                .TextAppearance_textFontWeight, /*defValue*/ FontStyle.FONT_WEIGHT_UNSPECIFIED);
 
         final String localeString = a.getString(com.android.internal.R.styleable
                 .TextAppearance_textLocale);
@@ -215,7 +215,7 @@
         mTextColorLink = linkColor;
         mTypeface = null;
 
-        mTextFontWeight = -1;
+        mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
         mTextLocales = null;
 
         mShadowRadius = 0.0f;
@@ -359,8 +359,8 @@
     }
 
     /**
-     * Returns the text font weight specified by this span, or <code>-1</code>
-     * if it does not specify one.
+     * Returns the text font weight specified by this span, or
+     * <code>FontStyle.FONT_WEIGHT_UNSPECIFIED</code> if it does not specify one.
      */
     public int getTextFontWeight() {
         return mTextFontWeight;
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 0769f12..91270d4 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Singular controller of insets to use when there isn't another obvious controller available.
@@ -48,10 +49,10 @@
     /**
      * @see IWindow#showInsets
      */
-    void showInsets(int types, boolean fromIme);
+    void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     /**
      * @see IWindow#hideInsets
      */
-    void hideInsets(int types, boolean fromIme);
+    void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index a856474..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -29,6 +29,7 @@
 import android.view.IScrollCaptureResponseListener;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -68,16 +69,18 @@
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to show
      * @param fromIme true if this request originated from IME (InputMethodService).
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    void showInsets(int types, boolean fromIme);
+    void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     /**
      * Called when a set of insets source window should be hidden by policy.
      *
      * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
      * @param fromIme true if this request originated from IME (InputMethodService).
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    void hideInsets(int types, boolean fromIme);
+    void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken);
 
     void moved(int newX, int newY);
     void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 27b4d87..e775969 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -58,6 +58,7 @@
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -68,7 +69,7 @@
  * Implements {@link WindowInsetsAnimationController}
  * @hide
  */
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class InsetsAnimationControlImpl implements InternalInsetsAnimationController,
         InsetsAnimationControlRunner {
 
@@ -96,6 +97,8 @@
     /** @see WindowInsetsAnimationController#hasZeroInsetsIme */
     private final boolean mHasZeroInsetsIme;
     private final CompatibilityInfo.Translator mTranslator;
+    @Nullable
+    private final ImeTracker.Token mStatsToken;
     private Insets mCurrentInsets;
     private Insets mPendingInsets;
     private float mPendingFraction;
@@ -114,7 +117,7 @@
             @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
             Interpolator interpolator, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            CompatibilityInfo.Translator translator) {
+            CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -152,6 +155,7 @@
         mAnimationType = animationType;
         mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation;
         mTranslator = translator;
+        mStatsToken = statsToken;
         mController.startAnimation(this, listener, types, mAnimation,
                 new Bounds(mHiddenInsets, mShownInsets));
     }
@@ -228,6 +232,11 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        return mStatsToken;
+    }
+
+    @Override
     public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
         setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
     }
@@ -253,10 +262,10 @@
         }
     }
 
-    @VisibleForTesting
     /**
      * @return Whether the finish callback of this animation should be invoked.
      */
+    @VisibleForTesting
     public boolean applyChangeInsets(@Nullable InsetsState outState) {
         if (mCancelled) {
             if (DEBUG) Log.d(TAG, "applyChangeInsets canceled");
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 291351e..cf40e7e 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -16,10 +16,12 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsController.AnimationType;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Interface representing a runner for an insets animation.
@@ -74,6 +76,12 @@
     @AnimationType int getAnimationType();
 
     /**
+     * @return The token tracking the current IME request or {@code null} otherwise.
+     */
+    @Nullable
+    ImeTracker.Token getStatsToken();
+
+    /**
      *
      * Export the state of classes that implement this interface into a protocol buffer
      * output stream.
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc97541..f7b9aa2 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -34,6 +34,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the
@@ -112,12 +113,13 @@
             @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs,
             Interpolator interpolator, @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            CompatibilityInfo.Translator translator, Handler mainThreadHandler) {
+            CompatibilityInfo.Translator translator, Handler mainThreadHandler,
+            @Nullable ImeTracker.Token statsToken) {
         mMainThreadHandler = mainThreadHandler;
         mOuterCallbacks = controller;
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
                 mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
-                translator);
+                translator, statsToken);
         InsetsAnimationThread.getHandler().post(() -> {
             if (mControl.isCancelled()) {
                 return;
@@ -141,6 +143,11 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        return mControl.getStatsToken();
+    }
+
+    @Override
     @UiThread
     public int getTypes() {
         return mControl.getTypes();
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 35838a3..fbd8226 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -44,6 +44,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -60,6 +61,7 @@
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -929,10 +931,12 @@
         hideTypes[0] &= ~animatingTypes;
 
         if (showTypes[0] != 0) {
-            applyAnimation(showTypes[0], true /* show */, false /* fromIme */);
+            applyAnimation(showTypes[0], true /* show */, false /* fromIme */,
+                    null /* statsToken */);
         }
         if (hideTypes[0] != 0) {
-            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
+            applyAnimation(hideTypes[0], false /* show */, false /* fromIme */,
+                    null /* statsToken */);
         }
 
         if (mControllableTypes != controllableTypes) {
@@ -948,11 +952,12 @@
 
     @Override
     public void show(@InsetsType int types) {
-        show(types, false /* fromIme */);
+        show(types, false /* fromIme */, null /* statsToken */);
     }
 
-    @VisibleForTesting
-    public void show(@InsetsType int types, boolean fromIme) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void show(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         if ((types & ime()) != 0) {
             Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")");
         }
@@ -979,7 +984,7 @@
                     true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
                     pendingRequest.animationType,
                     pendingRequest.layoutInsetsDuringAnimation,
-                    pendingRequest.useInsetsAnimationThread);
+                    pendingRequest.useInsetsAnimationThread, statsToken);
             return;
         }
 
@@ -990,8 +995,9 @@
             if ((types & type) == 0) {
                 continue;
             }
-            final @AnimationType int animationType = getAnimationType(type);
+            @AnimationType final int animationType = getAnimationType(type);
             final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            final boolean isImeAnimation = type == ime();
             if (requestedVisible && animationType == ANIMATION_TYPE_NONE
                     || animationType == ANIMATION_TYPE_SHOW) {
                 // no-op: already shown or animating in (because window visibility is
@@ -999,25 +1005,36 @@
                 if (DEBUG) Log.d(TAG, String.format(
                         "show ignored for type: %d animType: %d requestedVisible: %s",
                         type, animationType, requestedVisible));
+                if (isImeAnimation) {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
                 continue;
             }
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
+                if (isImeAnimation) {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
                 continue;
             }
+            if (isImeAnimation) {
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+            }
             typesReady |= type;
         }
         if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
-        applyAnimation(typesReady, true /* show */, fromIme);
+        applyAnimation(typesReady, true /* show */, fromIme, statsToken);
     }
 
     @Override
     public void hide(@InsetsType int types) {
-        hide(types, false /* fromIme */);
+        hide(types, false /* fromIme */, null /* statsToken */);
     }
 
     @VisibleForTesting
-    public void hide(@InsetsType int types, boolean fromIme) {
+    public void hide(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hide",
                     mHost.getInputMethodManager(), null /* icProto */);
@@ -1030,16 +1047,25 @@
             if ((types & type) == 0) {
                 continue;
             }
-            final @AnimationType int animationType = getAnimationType(type);
+            @AnimationType final int animationType = getAnimationType(type);
             final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            final boolean isImeAnimation = type == ime();
             if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
                     || animationType == ANIMATION_TYPE_HIDE) {
-                // no-op: already hidden or animating out.
+                // no-op: already hidden or animating out (because window visibility is
+                // applied before starting animation).
+                if (isImeAnimation) {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+                }
                 continue;
             }
+            if (isImeAnimation) {
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+            }
             typesReady |= type;
         }
-        applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
+        applyAnimation(typesReady, false /* show */, fromIme, statsToken);
     }
 
     @Override
@@ -1068,7 +1094,7 @@
 
         controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
                 interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
-                false /* useInsetsAnimationThread */);
+                false /* useInsetsAnimationThread */, null /* statsToken */);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
@@ -1077,7 +1103,8 @@
             long durationMs, Interpolator interpolator,
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
-            boolean useInsetsAnimationThread) {
+            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
             throw new IllegalStateException("Cannot start a new insets animation of "
                     + Type.toString(types)
@@ -1152,14 +1179,16 @@
                 ? new InsetsAnimationThreadControlRunner(controls,
                         frame, mState, listener, typesReady, this, durationMs, interpolator,
                         animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
-                        mHost.getHandler())
+                        mHost.getHandler(), statsToken)
                 : new InsetsAnimationControlImpl(controls,
                         frame, mState, listener, typesReady, this, durationMs, interpolator,
-                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator());
+                        animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
+                        statsToken);
         if ((typesReady & WindowInsets.Type.ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
                     mHost.getInputMethodManager(), null /* icProto */);
         }
+        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);
@@ -1311,11 +1340,18 @@
             // requested visibility.
             return;
         }
+        final ImeTracker.Token statsToken = runner.getStatsToken();
         if (shown) {
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
             showDirectly(runner.getTypes(), true /* fromIme */);
+            ImeTracker.get().onShown(statsToken);
         } else {
+            ImeTracker.get().onProgress(statsToken,
+                    ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
             hideDirectly(runner.getTypes(), true /* animationFinished */,
                     runner.getAnimationType(), true /* fromIme */);
+            ImeTracker.get().onHidden(statsToken);
         }
     }
 
@@ -1339,10 +1375,19 @@
     }
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
-        if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s",
-                control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         if (invokeCallback) {
+            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.get().onProgress(control.getStatsToken(),
+                    ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
+        }
+        if (DEBUG) {
+            Log.d(TAG, TextUtils.formatSimple(
+                    "cancelAnimation of types: %d, animType: %d, host: %s",
+                    control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         }
         boolean stateChanged = false;
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
@@ -1452,7 +1497,8 @@
     }
 
     @VisibleForTesting
-    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) {
+    public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         // TODO(b/166736352): We should only skip the animation of specific types, not all types.
         boolean skipAnim = false;
         if ((types & ime()) != 0) {
@@ -1465,12 +1511,12 @@
                         && consumer.hasViewFocusWhenWindowFocusGain();
             }
         }
-        applyAnimation(types, show, fromIme, skipAnim);
+        applyAnimation(types, show, fromIme, skipAnim, statsToken);
     }
 
     @VisibleForTesting
     public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme,
-            boolean skipAnim) {
+            boolean skipAnim, @Nullable ImeTracker.Token statsToken) {
         if (types == 0) {
             // nothing to animate.
             if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
@@ -1490,12 +1536,11 @@
                 listener.getDurationMs(), listener.getInsetsInterpolator(),
                 show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                !hasAnimationCallbacks /* useInsetsAnimationThread */);
+                !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
     }
 
-    private void hideDirectly(
-            @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
-            boolean fromIme) {
+    private void hideDirectly(@InsetsType int types, boolean animationFinished,
+            @AnimationType int animationType, boolean fromIme) {
         if ((types & ime()) != 0) {
             ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
                     mHost.getInputMethodManager(), null /* icProto */);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index edcfc95..778c677 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -37,6 +37,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Runs a fake animation of resizing insets to produce insets animation callbacks.
@@ -92,6 +93,12 @@
     }
 
     @Override
+    public ImeTracker.Token getStatsToken() {
+        // Return null as resizing the IME view is not explicitly tracked.
+        return null;
+    }
+
+    @Override
     public void cancel() {
         if (mCancelled || mFinished) {
             return;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 43f0947..63602393 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -192,6 +192,7 @@
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
 import android.view.contentcapture.MainContentCaptureSession;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Scroller;
 import android.window.ClientWindowFrames;
@@ -5649,17 +5650,23 @@
                     break;
                 }
                 case MSG_SHOW_INSETS: {
+                    final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
                     if (mView == null) {
                         Log.e(TAG,
                                 String.format("Calling showInsets(%d,%b) on window that no longer"
                                         + " has views.", msg.arg1, msg.arg2 == 1));
                     }
                     clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
-                    mInsetsController.show(msg.arg1, msg.arg2 == 1);
+                    mInsetsController.show(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
                 }
                 case MSG_HIDE_INSETS: {
-                    mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+                    final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
+                    mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
                     break;
                 }
                 case MSG_WINDOW_MOVED:
@@ -8811,12 +8818,14 @@
         mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
     }
 
-    private void showInsets(@InsetsType int types, boolean fromIme) {
-        mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    private void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
+        mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
     }
 
-    private void hideInsets(@InsetsType int types, boolean fromIme) {
-        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+    private void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
+        mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget();
     }
 
     public void dispatchMoved(int newX, int newY) {
@@ -10180,7 +10189,8 @@
         }
 
         @Override
-        public void showInsets(@InsetsType int types, boolean fromIme) {
+        public void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (fromIme) {
                 ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets",
@@ -10188,13 +10198,16 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                viewAncestor.showInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+                viewAncestor.showInsets(types, fromIme, statsToken);
+            } else {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
             }
         }
 
         @Override
-        public void hideInsets(@InsetsType int types, boolean fromIme) {
-
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (fromIme) {
                 ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets",
@@ -10202,7 +10215,10 @@
                         null /* icProto */);
             }
             if (viewAncestor != null) {
-                viewAncestor.hideInsets(types, fromIme);
+                ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+                viewAncestor.hideInsets(types, fromIme, statsToken);
+            } else {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
             }
         }
 
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index a66c67b..6eae63a 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -275,15 +275,16 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
+            @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
-            return service.showSoftInput(
-                    client, windowToken, flags, lastClickToolType, resultReceiver, reason);
+            return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+                    resultReceiver, reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -291,14 +292,15 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, @Nullable ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, int flags,
+            @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
-            return service.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+            return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+                    reason);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/ImeTracker.aidl b/core/java/android/view/inputmethod/ImeTracker.aidl
new file mode 100644
index 0000000..1988f48
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.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 ImeTracker.Token;
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
new file mode 100644
index 0000000..f4ecdff
--- /dev/null
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -0,0 +1,488 @@
+/*
+ * 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 static android.view.inputmethod.ImeTracker.Debug.originToString;
+import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+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 com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+/** @hide */
+public interface ImeTracker {
+
+    String TAG = "ImeTracker";
+
+    /**
+     * The origin of the IME request
+     *
+     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+     * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+     */
+    @IntDef(prefix = { "ORIGIN_" }, value = {
+            ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+            ORIGIN_SERVER_START_INPUT,
+            ORIGIN_SERVER_HIDE_INPUT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Origin {}
+
+    /**
+     * The IME show request originated in the client.
+     */
+    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+
+    /**
+     * The IME hide request originated in the client.
+     */
+    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+
+    /**
+     * The IME show request originated in the server.
+     */
+    int ORIGIN_SERVER_START_INPUT = 2;
+
+    /**
+     * The IME hide request originated in the server.
+     */
+    int ORIGIN_SERVER_HIDE_INPUT = 3;
+
+    /**
+     * The current phase of the IME request.
+     *
+     * The name follows the format {@code PHASE_x_...} where {@code x} denotes
+     * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
+     */
+    @IntDef(prefix = { "PHASE_" }, value = {
+            PHASE_CLIENT_VIEW_SERVED,
+            PHASE_SERVER_CLIENT_KNOWN,
+            PHASE_SERVER_CLIENT_FOCUSED,
+            PHASE_SERVER_ACCESSIBILITY,
+            PHASE_SERVER_SYSTEM_READY,
+            PHASE_SERVER_HIDE_IMPLICIT,
+            PHASE_SERVER_HIDE_NOT_ALWAYS,
+            PHASE_SERVER_WAIT_IME,
+            PHASE_SERVER_HAS_IME,
+            PHASE_SERVER_SHOULD_HIDE,
+            PHASE_IME_WRAPPER,
+            PHASE_IME_WRAPPER_DISPATCH,
+            PHASE_IME_SHOW_SOFT_INPUT,
+            PHASE_IME_HIDE_SOFT_INPUT,
+            PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE,
+            PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER,
+            PHASE_SERVER_APPLY_IME_VISIBILITY,
+            PHASE_WM_SHOW_IME_RUNNER,
+            PHASE_WM_SHOW_IME_READY,
+            PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET,
+            PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS,
+            PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS,
+            PHASE_WM_REMOTE_INSETS_CONTROLLER,
+            PHASE_WM_ANIMATION_CREATE,
+            PHASE_WM_ANIMATION_RUNNING,
+            PHASE_CLIENT_SHOW_INSETS,
+            PHASE_CLIENT_HIDE_INSETS,
+            PHASE_CLIENT_HANDLE_SHOW_INSETS,
+            PHASE_CLIENT_HANDLE_HIDE_INSETS,
+            PHASE_CLIENT_APPLY_ANIMATION,
+            PHASE_CLIENT_CONTROL_ANIMATION,
+            PHASE_CLIENT_ANIMATION_RUNNING,
+            PHASE_CLIENT_ANIMATION_CANCEL,
+            PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
+            PHASE_CLIENT_ANIMATION_FINISHED_HIDE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Phase {}
+
+    /** The view that requested the IME has been served by the IMM. */
+    int PHASE_CLIENT_VIEW_SERVED = 0;
+
+    /** The IME client that requested the IME has window manager focus. */
+    int PHASE_SERVER_CLIENT_KNOWN = 1;
+
+    /** The IME client that requested the IME has IME focus. */
+    int PHASE_SERVER_CLIENT_FOCUSED = 2;
+
+    /** The IME request complies with the current accessibility settings. */
+    int PHASE_SERVER_ACCESSIBILITY = 3;
+
+    /** The server is ready to run third party code. */
+    int PHASE_SERVER_SYSTEM_READY = 4;
+
+    /** Checked the implicit hide request against any explicit show requests. */
+    int PHASE_SERVER_HIDE_IMPLICIT = 5;
+
+    /** Checked the not-always hide request against any forced show requests. */
+    int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+
+    /** The server is waiting for a connection to the IME. */
+    int PHASE_SERVER_WAIT_IME = 7;
+
+    /** The server has a connection to the IME. */
+    int PHASE_SERVER_HAS_IME = 8;
+
+    /** The server decided the IME should be hidden. */
+    int PHASE_SERVER_SHOULD_HIDE = 9;
+
+    /** Reached the IME wrapper. */
+    int PHASE_IME_WRAPPER = 10;
+
+    /** Dispatched from the IME wrapper to the IME. */
+    int PHASE_IME_WRAPPER_DISPATCH = 11;
+
+    /** Reached the IME' showSoftInput method. */
+    int PHASE_IME_SHOW_SOFT_INPUT = 12;
+
+    /** Reached the IME' hideSoftInput method. */
+    int PHASE_IME_HIDE_SOFT_INPUT = 13;
+
+    /** The server decided the IME should be shown. */
+    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+
+    /** Requested applying the IME visibility in the insets source consumer. */
+    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+
+    /** Applied the IME visibility. */
+    int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+
+    /** Created the show IME runner. */
+    int PHASE_WM_SHOW_IME_RUNNER = 17;
+
+    /** Ready to show IME. */
+    int PHASE_WM_SHOW_IME_READY = 18;
+
+    /** The Window Manager has a connection to the IME insets control target. */
+    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+
+    /** Reached the window insets control target's show insets method. */
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+
+    /** Reached the window insets control target's hide insets method. */
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+
+    /** Reached the remote insets control target's show insets method. */
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+
+    /** Reached the remote insets control target's hide insets method. */
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+
+    /** Reached the remote insets controller. */
+    int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+
+    /** Created the IME window insets show animation. */
+    int PHASE_WM_ANIMATION_CREATE = 25;
+
+    /** Started the IME window insets show animation. */
+    int PHASE_WM_ANIMATION_RUNNING = 26;
+
+    /** Reached the client's show insets method. */
+    int PHASE_CLIENT_SHOW_INSETS = 27;
+
+    /** Reached the client's hide insets method. */
+    int PHASE_CLIENT_HIDE_INSETS = 28;
+
+    /** Handling the IME window insets show request. */
+    int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+
+    /** Handling the IME window insets hide request. */
+    int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+
+    /** Applied the IME window insets show animation. */
+    int PHASE_CLIENT_APPLY_ANIMATION = 31;
+
+    /** Started the IME window insets show animation. */
+    int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+
+    /** Queued the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+
+    /** Cancelled the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+
+    /** Finished the IME window insets show animation. */
+    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+
+    /** Finished the IME window insets hide animation. */
+    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+
+    /**
+     * Called when an IME show request is created.
+     *
+     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     * @param origin the origin of the IME show request.
+     * @param reason the reason why the IME show request was created.
+     */
+    void onRequestShow(@Nullable Token token, @Origin int origin,
+            @SoftInputShowHideReason int reason);
+
+    /**
+     * Called when an IME hide request is created.
+     *
+     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     * @param origin the origin of the IME hide request.
+     * @param reason the reason why the IME hide request was created.
+     */
+    void onRequestHide(@Nullable Token token, @Origin int origin,
+            @SoftInputShowHideReason int reason);
+
+    /**
+     * Called when an IME request progresses to a further phase.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the new phase the IME request reached.
+     */
+    void onProgress(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request fails.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request failed at.
+     */
+    void onFailed(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request reached a flow that is not yet implemented.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request was currently at.
+     */
+    void onTodo(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when an IME request is cancelled.
+     *
+     * @param token the token tracking the current IME request or {@code null} otherwise.
+     * @param phase the phase the IME request was cancelled at.
+     */
+    void onCancelled(@Nullable Token token, @Phase int phase);
+
+    /**
+     * Called when the IME show request is successful.
+     *
+     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     */
+    void onShown(@Nullable Token token);
+
+    /**
+     * Called when the IME hide request is successful.
+     *
+     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     */
+    void onHidden(@Nullable Token token);
+
+    /**
+     * Get the singleton instance of this class.
+     *
+     * @return the singleton instance of this class
+     */
+    @NonNull
+    static ImeTracker get() {
+        return SystemProperties.getBoolean("persist.debug.imetracker", false)
+                ? LOGGER
+                : NOOP_LOGGER;
+    }
+
+    /** The singleton IME tracker instance. */
+    ImeTracker LOGGER = new ImeTracker() {
+
+        @Override
+        public void onRequestShow(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+        }
+
+        @Override
+        public void onRequestHide(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+                    + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+        }
+
+        @Override
+        public void onProgress(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onFailed(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onTodo(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onCancelled(@Nullable Token token, int phase) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+        }
+
+        @Override
+        public void onShown(@Nullable Token token) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onShown");
+        }
+
+        @Override
+        public void onHidden(@Nullable Token token) {
+            if (token == null) return;
+            Log.i(TAG, token.mTag + ": onHidden");
+        }
+    };
+
+    /** The singleton no-op IME tracker instance. */
+    ImeTracker NOOP_LOGGER = new ImeTracker() {
+
+        @Override
+        public void onRequestShow(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {}
+
+        @Override
+        public void onRequestHide(@Nullable Token token, int origin,
+                @SoftInputShowHideReason int reason) {}
+
+        @Override
+        public void onProgress(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onFailed(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onTodo(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onCancelled(@Nullable Token token, int phase) {}
+
+        @Override
+        public void onShown(@Nullable Token token) {}
+
+        @Override
+        public void onHidden(@Nullable Token token) {}
+    };
+
+    /** A token that tracks the progress of an IME request. */
+    class Token implements Parcelable {
+
+        private final IBinder mBinder;
+        private final String mTag;
+
+        public Token() {
+            this(ActivityThread.currentProcessName());
+        }
+
+        public Token(String component) {
+            this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
+        }
+
+        private Token(IBinder binder, String tag) {
+            mBinder = binder;
+            mTag = tag;
+        }
+
+        /** For Parcelable, no special marshalled objects. */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeStrongBinder(mBinder);
+            dest.writeString8(mTag);
+        }
+
+        @NonNull
+        public static final Creator<Token> CREATOR = new Creator<>() {
+            @Override
+            public Token createFromParcel(Parcel source) {
+                IBinder binder = source.readStrongBinder();
+                String tag = source.readString8();
+                return new Token(binder, tag);
+            }
+
+            @Override
+            public Token[] newArray(int size) {
+                return new Token[size];
+            }
+        };
+    }
+
+    /**
+     * Utilities for mapping phases and origins IntDef values to their names.
+     *
+     * Note: This is held in a separate class so that it only gets initialized when actually needed.
+     */
+    class Debug {
+
+        private static final Map<Integer, String> sOrigins =
+                getFieldMapping(ImeTracker.class, "ORIGIN_");
+        private static final Map<Integer, String> sPhases =
+                getFieldMapping(ImeTracker.class, "PHASE_");
+
+        public static String originToString(int origin) {
+            return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
+        }
+
+        public static String phaseToString(int phase) {
+            return sPhases.getOrDefault(phase, "PHASE_" + phase);
+        }
+
+        private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) {
+            return Arrays.stream(cls.getDeclaredFields())
+                    .filter(field -> field.getName().startsWith(fieldPrefix))
+                    .collect(Collectors.toMap(Debug::getFieldValue, Field::getName));
+        }
+
+        private static int getFieldValue(Field field) {
+            try {
+                return field.getInt(null);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 4d5a17d..92380ed 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -300,11 +300,12 @@
      * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call
      *        of {@link InputMethodManager#showSoftInput(View, int)} is associated with
      *        this callback.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      * @hide
      */
     @MainThread
-    default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-            IBinder showInputToken) {
+    public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
 
@@ -338,11 +339,14 @@
      * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call
      *         of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated
      *         with this callback.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      * @hide
      */
     @MainThread
-    public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-            IBinder hideInputToken);
+    public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+            IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) {
+        hideSoftInput(flags, resultReceiver);
+    }
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
@@ -369,7 +373,7 @@
 
     /**
      * Checks if IME is ready to start stylus handwriting session.
-     * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called.
+     * If yes, {@link #startStylusHandwriting(int, InputChannel, List)} is called.
      * @param requestId
      * @hide
      */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 74afced..ee31fd5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -632,6 +632,8 @@
 
     private final DelegateImpl mDelegate = new DelegateImpl();
 
+    private static boolean sPreventImeStartupUnlessTextEditor;
+
     // -----------------------------------------------------------
 
     private static final int MSG_DUMP = 1;
@@ -1435,6 +1437,10 @@
         // display case.
         final Looper looper = displayId == Display.DEFAULT_DISPLAY
                 ? Looper.getMainLooper() : context.getMainLooper();
+        // Keep track of whether to expect the IME to be unavailable so as to avoid log spam in
+        // sendInputEventOnMainLooperLocked() by not logging a verbose message on every DPAD event
+        sPreventImeStartupUnlessTextEditor = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         return forContextInternal(displayId, looper);
     }
 
@@ -2001,6 +2007,10 @@
 
     private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                reason);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
         // Re-dispatch if there is a context mismatch.
@@ -2012,10 +2022,13 @@
         checkFocus();
         synchronized (mH) {
             if (!hasServedByInputMethodLocked(view)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                 return false;
             }
 
+            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.
             mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
@@ -2024,6 +2037,7 @@
             return IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     view.getWindowToken(),
+                    statsToken,
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
@@ -2043,19 +2057,28 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
+            final ImeTracker.Token statsToken = new ImeTracker.Token();
+            ImeTracker.get().onRequestShow(statsToken, 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.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
                 return;
             }
+
+            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.
             mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
             IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
+                    statsToken,
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
@@ -2125,17 +2148,24 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, 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.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 return false;
             }
 
-            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags,
-                    resultReceiver, reason);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+                    flags, resultReceiver, reason);
         }
     }
 
@@ -2763,14 +2793,23 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT);
+
         synchronized (mH) {
             if (mCurRootView == null || mCurRootView.getView() == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
             IInputMethodManagerGlobalInvoker.hideSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
+                    statsToken,
                     HIDE_NOT_ALWAYS,
                     null,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -2839,15 +2878,24 @@
      * @hide
      */
     public void notifyImeHidden(IBinder windowToken) {
+        final ImeTracker.Token statsToken = new ImeTracker.Token();
+        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
         synchronized (mH) {
-            if (isImeSessionAvailableLocked() && mCurRootView != null
-                    && mCurRootView.getWindowToken() == windowToken) {
-                IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
-                        null /* resultReceiver */,
-                        SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+            if (!isImeSessionAvailableLocked() || mCurRootView == null
+                    || mCurRootView.getWindowToken() != windowToken) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+                return;
             }
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+
+            IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
+                    0 /* flags */, null /* resultReceiver */,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
         }
     }
 
@@ -3364,8 +3412,12 @@
                 return DISPATCH_IN_PROGRESS;
             }
 
-            Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
-                    + " dropping: " + event);
+            if (sPreventImeStartupUnlessTextEditor) {
+                Log.d(TAG, "Dropping event because IME is evicted: " + event);
+            } else {
+                Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+                        + " dropping: " + event);
+            }
         }
         return DISPATCH_NOT_HANDLED;
     }
@@ -3967,7 +4019,7 @@
 
         /**
          * As reported by {@link InputBindResult}. This value is determined by
-         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}.
          */
         final boolean mIsInputMethodSuppressingSpellChecker;
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 810fde2..bf1a2bd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -2289,11 +2289,13 @@
      * @param familyName family name string, e.g. "serif"
      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
      * @param style a typeface style
-     * @param weight a weight value for the Typeface or -1 if not specified.
+     * @param weight a weight value for the Typeface or {@code FontStyle.FONT_WEIGHT_UNSPECIFIED}
+     *               if not specified.
      */
     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
-            @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+            @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+                    int weight) {
         if (typeface == null && familyName != null) {
             // Lookup normal Typeface from system font map.
             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
@@ -2320,7 +2322,8 @@
     }
 
     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
-            @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
+            @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+                    int weight) {
         if (weight >= 0) {
             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
             final boolean italic = (style & Typeface.ITALIC) != 0;
@@ -4021,7 +4024,7 @@
         boolean mFontFamilyExplicit = false;
         int mTypefaceIndex = -1;
         int mTextStyle = 0;
-        int mFontWeight = -1;
+        int mFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
         boolean mAllCaps = false;
         int mShadowColor = 0;
         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
@@ -6946,18 +6949,18 @@
         if (isPassword) {
             setTransformationMethod(PasswordTransformationMethod.getInstance());
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
-                    Typeface.NORMAL, -1 /* weight, not specifeid */);
+                    Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
         } else if (isVisiblePassword) {
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
-                    Typeface.NORMAL, -1 /* weight, not specified */);
+                    Typeface.NORMAL, FontStyle.FONT_WEIGHT_UNSPECIFIED);
         } else if (wasPassword || wasVisiblePassword) {
             // not in password mode, clean up typeface and transformation
             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
-                    -1 /* weight, not specified */);
+                    FontStyle.FONT_WEIGHT_UNSPECIFIED);
             if (mTransformation == PasswordTransformationMethod.getInstance()) {
                 forceUpdate = true;
             }
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index a5aefd5..f55932e 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.annotation.NonNull;
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
@@ -142,6 +144,14 @@
      */
     public void onRunningAppsChanged(ArraySet<Integer> runningUids) {}
 
+    /**
+     * This is called when an Activity is entering PIP.
+     * Returns {@code true} if the Activity is allowed to enter PIP.
+     */
+    public boolean isEnteringPipAllowed(int uid) {
+        return isWindowingModeSupported(WINDOWING_MODE_PINNED);
+    }
+
     /** Dump debug data */
     public void dump(String prefix, final PrintWriter pw) {
         pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index c62fba9..1e3714e 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -21,6 +21,7 @@
 import android.view.InputChannel;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
@@ -69,9 +70,11 @@
 
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
 
-    void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
+    void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken,
+            int flags, in ResultReceiver resultReceiver);
 
-    void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
+    void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken,
+            int flags, in ResultReceiver resultReceiver);
 
     void updateEditorToolType(int toolType);
 
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index 4babb70..f77e962 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.inputmethod;
 
 import android.net.Uri;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.infra.AndroidFuture;
@@ -41,7 +42,8 @@
     void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */);
     void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */);
     void notifyUserActionAsync();
-    void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible);
+    void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+            in @nullable ImeTracker.Token statsToken);
     void onStylusHandwritingReady(int requestId, int pid);
     void resetStylusHandwriting(int requestId);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 67c2103..66e3333 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -25,6 +25,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.View;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -100,7 +101,7 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}.
+     * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int)}.
      *
      * @param vis visibility flags
      * @param backDisposition disposition flags
@@ -250,7 +251,7 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)}
+     * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
      *
      * @param flags additional operating flags
      * @param reason the reason to hide soft input
@@ -316,7 +317,7 @@
 
     /**
      * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean,
-     * IBooleanResultCallback)}
+     * AndroidFuture)}
      *
      * @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same
      *                       IME
@@ -375,22 +376,25 @@
     }
 
     /**
-     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}.
+     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean,
+     * ImeTracker.Token)}.
      *
      * @param showOrHideInputToken placeholder token that maps to window requesting
      *        {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or
-     *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow
-     *        (IBinder, int)}
+     *        {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder,
+     *        int)}
      * @param setVisible {@code true} to set IME visible, else hidden.
+     * @param statsToken the token tracking the current IME request or {@code null} otherwise.
      */
     @AnyThread
-    public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) {
+    public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible,
+            @Nullable ImeTracker.Token statsToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
         }
         try {
-            ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible);
+            ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index fe77236..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.view;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
@@ -31,6 +32,7 @@
 import android.view.PointerIcon;
 import android.view.ScrollCaptureResponse;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -66,11 +68,13 @@
     }
 
     @Override
-    public void showInsets(@InsetsType int types, boolean fromIme) {
+    public void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     @Override
-    public void hideInsets(@InsetsType int types, boolean fromIme) {
+    public void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 40c6a05..00bc3f2 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.os.ResultReceiver;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.EditorInfo;
@@ -54,11 +55,12 @@
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
     InputMethodSubtype getLastInputMethodSubtype(int userId);
 
-    boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
-            int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
-    boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
+    boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+            in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
             in @nullable ResultReceiver resultReceiver, int reason);
-
+    boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
+            in @nullable ImeTracker.Token statsToken, int flags,
+            in @nullable ResultReceiver resultReceiver, int reason);
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'editorInfo' is non-null then also does startInput.
     // @NonNull
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
index 80d1457..7b154a5 100644
--- a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -18,7 +18,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.os.Trace;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -55,7 +54,6 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Trace.beginSection("RemeasuringLinearLayout#onMeasure");
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int count = getChildCount();
         int height = 0;
@@ -88,6 +86,5 @@
         }
         mMatchParentViews.clear();
         setMeasuredDimension(getMeasuredWidth(), height);
-        Trace.endSection();
     }
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0218215..ac383e6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1855,6 +1855,10 @@
     -->
     <string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string>
 
+    <!-- The package name of the dock manager app. Must be granted the
+         POST_NOTIFICATIONS permission. -->
+    <string name="config_defaultDockManagerPackageName" translatable="false"></string>
+
     <!-- Whether to enable geocoder overlay which allows geocoder to be replaced
          by an app at run-time. When disabled, only the
          config_geocoderProviderPackageName package will be searched for
@@ -2422,8 +2426,8 @@
     <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
     <!-- Limit of how long the device can remain unlocked due to attention checking.  -->
     <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes.  -->
-    <!-- Is the system user the only user allowed to dream. -->
-    <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+    <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
+    <bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
     <!-- Whether dreams are disabled when ambient mode is suppressed. -->
     <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
 
@@ -2672,9 +2676,9 @@
          Should be false for most devices, except automotive vehicle with passenger displays. -->
     <bool name="config_multiuserUsersOnSecondaryDisplays">false</bool>
 
-    <!-- Whether to automatically switch a non-primary user back to the primary user after a
-         timeout when the device is docked.  -->
-    <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+    <!-- Whether to automatically switch to the designated Dock User (the user chosen for
+         displaying dreams, etc.) after a timeout when the device is docked.  -->
+    <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
 
     <!-- Whether to only install system packages on a user if they're allowlisted for that user
          type. These are flags and can be freely combined.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48484c7..216975d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6331,6 +6331,8 @@
     <string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
     <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
     <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
+    <!-- Error message indicating the user cannot view picture-in-picture when running on a virtual device. [CHAR LIMIT=NONE] -->
+    <string name="vdm_pip_blocked">Can’t view picture-in-picture while streaming</string>
 
     <!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
     <string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b5d534f..89ec5ba 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -466,7 +466,7 @@
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="bool" name="config_multiuserUsersOnSecondaryDisplays" />
-  <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
+  <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -2236,7 +2236,7 @@
   <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
   <java-symbol type="string" name="config_dreamsDefaultComponent" />
   <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
-  <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+  <java-symbol type="bool" name="config_dreamsOnlyEnabledForDockUser" />
   <java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
   <java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
   <java-symbol type="array" name="config_supportedDreamComplications" />
@@ -3480,6 +3480,9 @@
   <!-- Captive Portal Login -->
   <java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" />
 
+  <!-- Dock Manager -->
+  <java-symbol type="string" name="config_defaultDockManagerPackageName" />
+
   <!-- Optional IPsec algorithms -->
   <java-symbol type="array" name="config_optionalIpSecAlgorithms" />
 
@@ -4858,6 +4861,7 @@
   <!-- For VirtualDeviceManager -->
   <java-symbol type="string" name="vdm_camera_access_denied" />
   <java-symbol type="string" name="vdm_secure_window" />
+  <java-symbol type="string" name="vdm_pip_blocked" />
 
   <java-symbol type="color" name="camera_privacy_light_day"/>
   <java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
index 7663150..df6b7b2 100644
--- a/core/res/res/xml/config_user_types.xml
+++ b/core/res/res/xml/config_user_types.xml
@@ -83,6 +83,8 @@
 For profile and full users:
     default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
     enabled
+    user-properties
+    max-allowed
 For profile users only:
     max-allowed-per-parent
     icon-badge
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
         if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
             resultString = "Successfully registered, but expected it to fail";
             try {
-                context.registerReceiver(this, new IntentFilter("foo.bar"));
+                context.registerReceiver(this, new IntentFilter("foo.bar"),
+                        Context.RECEIVER_EXPORTED_UNAUDITED);
                 context.unregisterReceiver(this);
             } catch (ReceiverCallNotAllowedException e) {
                 //resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
                 pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
                 mContext.unregisterReceiver(this);
             }
-        }, new IntentFilter(ACTION_SERVICE_STARTED));
+        }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
 
         serviceTrigger.run();
         try {
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7ebebc9..c59a3f5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -246,10 +246,12 @@
     @Test
     public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(1f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(2f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
                 .build();
@@ -258,6 +260,7 @@
 
         assertTrue(Float.isNaN(info.getQFactor()));
         assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+        assertEmptyFrequencyProfileAndControl(info);
 
         // One vibrator with values undefined.
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
@@ -266,16 +269,19 @@
 
         assertTrue(Float.isNaN(info.getQFactor()));
         assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(10f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setQFactor(10f)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 11, 5, 1, null))
@@ -285,113 +291,131 @@
 
         assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
         assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+
+        // No frequency range defined.
+        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
     }
 
     @Test
     public void getFrequencyProfile_noVibrator_returnsEmpty() {
         VibratorInfo info = new SystemVibrator.NoVibratorInfo();
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, differentResonantFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_missingValues_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
                         new float[] { 0, 1 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingResonantFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingMinFrequency});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
                         new float[] { 0, 1 }))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
 
         VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
                 .build();
         info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 0, 1, 1, 0 }))
                 .build();
         VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
 
-        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEmptyFrequencyProfileAndControl(info);
     }
 
     @Test
     public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
         VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
                         new float[] { 0.5f, 1, 1, 0.5f }))
                 .build();
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 1, 1, 1 }))
                 .build();
         VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+                .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
                 .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
                         new float[] { 0.8f, 1, 0.8f, 0.5f }))
                 .build();
@@ -401,6 +425,20 @@
         assertEquals(
                 new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
                 info.getFrequencyProfile());
+        assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+        // Third vibrator without frequency control capability.
+        thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+                        new float[] { 0.8f, 1, 0.8f, 0.5f }))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+        assertEquals(
+                new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+                info.getFrequencyProfile());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
     }
 
     @Test
@@ -547,4 +585,12 @@
         VibrationAttributes vibrationAttributes = captor.getValue();
         assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
     }
+
+    /**
+     * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+     */
+    void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+        assertTrue(info.getFrequencyProfile().isEmpty());
+        assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index ddcb175..0bf133f 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -98,12 +99,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // test if setVisibility can show IME
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
 
             // test if setVisibility can hide IME
-            mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
         });
@@ -117,7 +118,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Request IME visible before control is available.
             mImeConsumer.onWindowFocusGained(true);
-            mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
 
             // set control and verify visibility is applied.
             InsetsSourceControl control =
@@ -125,9 +126,11 @@
             mController.onControlsChanged(new InsetsSourceControl[] { control });
             // IME show animation should be triggered when control becomes available.
             verify(mController).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */);
+                    eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */,
+                    any() /* statsToken */);
             verify(mController, never()).applyAnimation(
-                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */);
+                    eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */,
+                    any() /* statsToken */);
         });
     }
 
@@ -153,7 +156,8 @@
             mImeConsumer.onWindowFocusGained(hasWindowFocus);
             final boolean imeVisible = hasWindowFocus && hasViewFocus;
             if (imeVisible) {
-                mController.show(WindowInsets.Type.ime(), true /* fromIme */);
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+                        null /* statsToken */);
             }
 
             // set control and verify visibility is applied.
@@ -169,20 +173,21 @@
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(false) /* fromIme */,
-                        eq(expectSkipAnim) /* skipAnim */);
+                        eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
             }
 
             // If previously hasViewFocus is false, verify when requesting the IME visible next
             // time will not skip animation.
             if (!hasViewFocus) {
-                mController.show(WindowInsets.Type.ime(), true);
+                mController.show(WindowInsets.Type.ime(), true /* fromIme */,
+                        null /* statsToken */);
                 mController.onControlsChanged(new InsetsSourceControl[]{ control });
                 // Verify IME show animation should be triggered when control becomes available and
                 // the animation will be skipped by getAndClearSkipAnimationOnce invoked.
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(true) /* fromIme */,
-                        eq(false) /* skipAnim */);
+                        eq(false) /* skipAnim */, null /* statsToken */);
             }
         });
     }
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index e9cd8ad..c88255e 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -109,7 +109,8 @@
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */, new LinearInterpolator(),
-                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */);
+                0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+                null /* statsToken */);
         mController.setReadyDispatched(true);
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 409bae8..c6fa778 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -248,7 +248,7 @@
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             verify(loggingListener).onReady(notNull(), anyInt());
         });
     }
@@ -260,14 +260,14 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
             final @InsetsType int types = navigationBars() | statusBars() | ime();
             assertEquals(types, mController.getRequestedVisibleTypes() & types);
 
-            mController.hide(ime(), true /* fromIme */);
+            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.hide(all());
             mController.cancelExistingAnimations();
             assertEquals(0, mController.getRequestedVisibleTypes() & types);
@@ -282,10 +282,10 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
-            mController.show(ime(), true /* fromIme */);
+            mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
-            mController.hide(ime(), true /* fromIme */);
+            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, ime()));
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
@@ -452,7 +452,7 @@
             assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
@@ -476,7 +476,7 @@
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
@@ -554,7 +554,7 @@
             verify(listener, never()).onReady(any(), anyInt());
 
             // Pretend that IME is calling.
-            mController.show(ime(), true);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -638,7 +638,7 @@
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
             copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
@@ -845,7 +845,7 @@
 
             // Showing invisible ime should only causes insets change once.
             clearInvocations(mTestHost);
-            mController.show(ime(), true /* fromIme */);
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
diff --git a/graphics/java/android/graphics/fonts/FontStyle.java b/graphics/java/android/graphics/fonts/FontStyle.java
index 09799fd..48969aa 100644
--- a/graphics/java/android/graphics/fonts/FontStyle.java
+++ b/graphics/java/android/graphics/fonts/FontStyle.java
@@ -48,6 +48,10 @@
     private static final String TAG = "FontStyle";
 
     /**
+     * A default value when font weight is unspecified
+     */
+    public static final int FONT_WEIGHT_UNSPECIFIED = -1;
+    /**
      * A minimum weight value for the font
      */
     public static final int FONT_WEIGHT_MIN = 1;
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
 
     <!-- Whether to dim a split-screen task when the other is the IME target -->
     <bool name="config_dimNonImeAttachedSide">true</bool>
+
+    <!-- Components support to launch multiple instances into split-screen -->
+    <string-array name="config_componentsSupportMultiInstancesSplit">
+    </string-array>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index dec1e38..065fd95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
 
 package com.android.wm.shell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
@@ -49,7 +47,6 @@
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -568,6 +565,22 @@
         }
     }
 
+    /**
+     * Return list of {@link RunningTaskInfo}s for the given display.
+     *
+     * @return filtered list of tasks or empty list
+     */
+    public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+        ArrayList<RunningTaskInfo> result = new ArrayList<>();
+        for (int i = 0; i < mTasks.size(); i++) {
+            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+            if (taskInfo.displayId == displayId) {
+                result.add(taskInfo);
+            }
+        }
+        return result;
+    }
+
     /** Gets running task by taskId. Returns {@code null} if no such task observed. */
     @Nullable
     public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -694,57 +707,6 @@
         taskListener.reparentChildSurfaceToTask(taskId, sc, t);
     }
 
-    /**
-     * Create a {@link WindowContainerTransaction} to clear task bounds.
-     *
-     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
-     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     *
-     * @param displayId display id for tasks that will have bounds cleared
-     * @return {@link WindowContainerTransaction} with pending operations to clear bounds
-     */
-    public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        for (int i = 0; i < mTasks.size(); i++) {
-            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
-            if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
-                    == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
-                        taskInfo.token, taskInfo);
-                wct.setBounds(taskInfo.token, null);
-            }
-        }
-        return wct;
-    }
-
-    /**
-     * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
-     *
-     * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
-     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     *
-     * @param displayId display id for tasks that will have windowing mode reset to {@link
-     *                  WindowConfiguration#WINDOWING_MODE_UNDEFINED}
-     * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
-     */
-    public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        for (int i = 0; i < mTasks.size(); i++) {
-            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
-            if (taskInfo.displayId == displayId
-                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
-                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
-                        taskInfo);
-                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
-            }
-        }
-        return wct;
-    }
-
     private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
             int event) {
         ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index b6327e5..d9eaeee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,7 +38,6 @@
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
@@ -69,7 +68,7 @@
  * Controls the window animation run when a user initiates a back gesture.
  */
 public class BackAnimationController implements RemoteCallable<BackAnimationController> {
-    private static final String TAG = "BackAnimationController";
+    private static final String TAG = "ShellBackPreview";
     private static final int SETTING_VALUE_OFF = 0;
     private static final int SETTING_VALUE_ON = 1;
     public static final boolean IS_ENABLED =
@@ -82,16 +81,15 @@
     /** Predictive back animation developer option */
     private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
     /**
-     * Max duration to wait for a transition to finish before accepting another gesture start
-     * request.
+     * Max duration to wait for an animation to finish before triggering the real back.
      */
-    private static final long MAX_TRANSITION_DURATION = 2000;
+    private static final long MAX_ANIMATION_DURATION = 2000;
 
     /** True when a back gesture is ongoing */
     private boolean mBackGestureStarted = false;
 
-    /** Tracks if an uninterruptible transition is in progress */
-    private boolean mTransitionInProgress = false;
+    /** Tracks if an uninterruptible animation is in progress */
+    private boolean mPostCommitAnimationInProgress = false;
     /** Tracks if we should start the back gesture on the next motion move event */
     private boolean mShouldStartOnNextMoveEvent = false;
     /** @see #setTriggerBack(boolean) */
@@ -105,9 +103,9 @@
     private final ShellController mShellController;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
-    private final Runnable mResetTransitionRunnable = () -> {
-        ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
-                MAX_TRANSITION_DURATION);
+    private final Runnable mAnimationTimeoutRunnable = () -> {
+        ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...",
+                MAX_ANIMATION_DURATION);
         onBackAnimationFinished();
     };
 
@@ -119,6 +117,8 @@
 
     private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
 
+    private IOnBackInvokedCallback mActiveCallback;
+
     @VisibleForTesting
     final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
         @Override
@@ -126,9 +126,9 @@
         @Override
         public void focusLost(IBinder inputToken) {
             mShellExecutor.execute(() -> {
-                if (!mBackGestureStarted || mTransitionInProgress) {
-                    // If an uninterruptible transition is already in progress, we should ignore
-                    // this due to the transition may cause focus lost. (alpha = 0)
+                if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
+                    // If an uninterruptible animation is already in progress, we should ignore
+                    // this due to it may cause focus lost. (alpha = 0)
                     return;
                 }
                 ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
@@ -180,26 +180,11 @@
     }
 
     private void initBackAnimationRunners() {
-        final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
-        final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
-            @Override
-            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                    IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
-                // Animation missing. Simply finish animation.
-                finishedCallback.onAnimationFinished();
-            }
-        };
-
-        final BackAnimationRunner dummyBackRunner =
-                new BackAnimationRunner(dummyCallback, dummyRunner);
         final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
                 new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
         // TODO (238474994): register cross activity animation when it's completed.
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
         // TODO (236760237): register dialog close animation when it's completed.
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -222,10 +207,9 @@
     private void updateEnableAnimationFromSetting() {
         int settingValue = Global.getInt(mContext.getContentResolver(),
                 Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
-        boolean isEnabled = settingValue == SETTING_VALUE_ON;
+        boolean isEnabled = settingValue == SETTING_VALUE_ON && IS_U_ANIMATION_ENABLED;
         mEnableAnimations.set(isEnabled);
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
-                isEnabled);
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
 
     public BackAnimation getBackAnimationImpl() {
@@ -279,7 +263,9 @@
         public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
                 IRemoteAnimationRunner runner) {
             executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
-                    (controller) -> controller.setBackToLauncherCallback(callback, runner));
+                    (controller) -> controller.registerAnimation(
+                            BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                            new BackAnimationRunner(callback, runner)));
         }
 
         @Override
@@ -294,44 +280,22 @@
         }
     }
 
-    @VisibleForTesting
-    void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                new BackAnimationRunner(callback, runner));
+    void registerAnimation(@BackNavigationInfo.BackTargetType int type,
+            @NonNull BackAnimationRunner runner) {
+        mAnimationDefinition.set(type, runner);
     }
 
     private void clearBackToLauncherCallback() {
         mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
     }
 
-    @VisibleForTesting
-    void onBackAnimationFinished() {
-        if (!mTransitionInProgress) {
-            return;
-        }
-
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
-
-        // Trigger real back.
-        if (mBackNavigationInfo != null) {
-            IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
-            if (mTriggerBack) {
-                dispatchOnBackInvoked(callback);
-            } else {
-                dispatchOnBackCancelled(callback);
-            }
-        }
-
-        finishBackNavigation();
-    }
-
     /**
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
      */
     public void onMotionEvent(float touchX, float touchY, int keyAction,
             @BackEvent.SwipeEdge int swipeEdge) {
-        if (mTransitionInProgress) {
+        if (mPostCommitAnimationInProgress) {
             return;
         }
 
@@ -348,7 +312,7 @@
                 onGestureStarted(touchX, touchY, swipeEdge);
                 mShouldStartOnNextMoveEvent = false;
             }
-            onMove(touchX, touchY, swipeEdge);
+            onMove();
         } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW,
                     "Finishing gesture with event action: %d", keyAction);
@@ -386,30 +350,22 @@
             return;
         }
         final int backType = backNavigationInfo.getType();
-        final IOnBackInvokedCallback targetCallback;
         final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
         if (shouldDispatchToAnimator) {
+            mActiveCallback = mAnimationDefinition.get(backType).getCallback();
             mAnimationDefinition.get(backType).startGesture();
         } else {
-            targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
-            dispatchOnBackStarted(targetCallback, mTouchTracker.createStartEvent(null));
+            mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+            dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
         }
     }
 
-    private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
+    private void onMove() {
         if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
             return;
         }
         final BackEvent backEvent = mTouchTracker.createProgressEvent();
-
-        int backType = mBackNavigationInfo.getType();
-        IOnBackInvokedCallback targetCallback;
-        if (shouldDispatchToAnimator(backType)) {
-            targetCallback = mAnimationDefinition.get(backType).getCallback();
-        } else {
-            targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
-        }
-        dispatchOnBackProgressed(targetCallback, backEvent);
+        dispatchOnBackProgressed(mActiveCallback, backEvent);
     }
 
     private void injectBackKey() {
@@ -431,57 +387,6 @@
         }
     }
 
-    private void onGestureFinished(boolean fromTouch) {
-        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
-        if (!mBackGestureStarted) {
-            finishBackNavigation();
-            return;
-        }
-
-        if (fromTouch) {
-            // Let touch reset the flag otherwise it will start a new back navigation and refresh
-            // the info when received a new move event.
-            mBackGestureStarted = false;
-        }
-
-        if (mTransitionInProgress) {
-            return;
-        }
-
-        if (mBackNavigationInfo == null) {
-            // No focus window found or core are running recents animation, inject back key as
-            // legacy behavior.
-            if (mTriggerBack) {
-                injectBackKey();
-            }
-            finishBackNavigation();
-            return;
-        }
-
-        int backType = mBackNavigationInfo.getType();
-        boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
-        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
-        IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
-                ? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
-
-        if (shouldDispatchToAnimator) {
-            if (runner.onGestureFinished(mTriggerBack)) {
-                Log.w(TAG, "Gesture released, but animation didn't ready.");
-                return;
-            }
-            startTransition();
-        }
-        if (mTriggerBack) {
-            dispatchOnBackInvoked(targetCallback);
-        } else {
-            dispatchOnBackCancelled(targetCallback);
-        }
-        if (!shouldDispatchToAnimator) {
-            // Animation callback missing. Simply finish animation.
-            finishBackNavigation();
-        }
-    }
-
     private boolean shouldDispatchToAnimator(int backType) {
         return mEnableAnimations.get()
                 && mBackNavigationInfo != null
@@ -495,7 +400,7 @@
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackStarted(backEvent);
             }
         } catch (RemoteException e) {
@@ -519,7 +424,7 @@
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackCancelled();
             }
         } catch (RemoteException e) {
@@ -533,7 +438,7 @@
             return;
         }
         try {
-            if (shouldDispatchAnimation(callback)) {
+            if (mEnableAnimations.get()) {
                 callback.onBackProgressed(backEvent);
             }
         } catch (RemoteException e) {
@@ -541,17 +446,11 @@
         }
     }
 
-    private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
-        return (IS_U_ANIMATION_ENABLED || callback == mAnimationDefinition.get(
-                BackNavigationInfo.TYPE_RETURN_TO_HOME).getCallback())
-                && mEnableAnimations.get();
-    }
-
     /**
      * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
      */
     public void setTriggerBack(boolean triggerBack) {
-        if (mTransitionInProgress) {
+        if (mPostCommitAnimationInProgress) {
             return;
         }
         mTriggerBack = triggerBack;
@@ -562,6 +461,109 @@
         mTouchTracker.setProgressThreshold(progressThreshold);
     }
 
+    /**
+     * Called when the gesture is released, then it could start the post commit animation.
+     */
+    private void onGestureFinished(boolean fromTouch) {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+        if (!mBackGestureStarted) {
+            finishBackNavigation();
+            return;
+        }
+
+        if (fromTouch) {
+            // Let touch reset the flag otherwise it will start a new back navigation and refresh
+            // the info when received a new move event.
+            mBackGestureStarted = false;
+        }
+
+        if (mPostCommitAnimationInProgress) {
+            ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running");
+            return;
+        }
+
+        if (mBackNavigationInfo == null) {
+            // No focus window found or core are running recents animation, inject back key as
+            // legacy behavior.
+            if (mTriggerBack) {
+                injectBackKey();
+            }
+            finishBackNavigation();
+            return;
+        }
+
+        final int backType = mBackNavigationInfo.getType();
+        // Directly finish back navigation if no animator defined.
+        if (!shouldDispatchToAnimator(backType)) {
+            if (mTriggerBack) {
+                dispatchOnBackInvoked(mActiveCallback);
+            } else {
+                dispatchOnBackCancelled(mActiveCallback);
+            }
+            // Animation missing. Simply finish back navigation.
+            finishBackNavigation();
+            return;
+        }
+
+        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
+        if (runner.isWaitingAnimation()) {
+            ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+            return;
+        }
+        startPostCommitAnimation();
+    }
+
+    /**
+     * Start the phase 2 animation when gesture is released.
+     * Callback to {@link #onBackAnimationFinished} when it is finished or timeout.
+     */
+    private void startPostCommitAnimation() {
+        if (mPostCommitAnimationInProgress) {
+            return;
+        }
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
+        mPostCommitAnimationInProgress = true;
+        mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
+
+        // The next callback should be {@link #onBackAnimationFinished}.
+        if (mTriggerBack) {
+            dispatchOnBackInvoked(mActiveCallback);
+        } else {
+            dispatchOnBackCancelled(mActiveCallback);
+        }
+    }
+
+    /**
+     * Called when the post commit animation is completed or timeout.
+     * This will trigger the real {@link IOnBackInvokedCallback} behavior.
+     */
+    @VisibleForTesting
+    void onBackAnimationFinished() {
+        if (!mPostCommitAnimationInProgress) {
+            return;
+        }
+        // Stop timeout runner.
+        mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
+        mPostCommitAnimationInProgress = false;
+
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
+
+        // Trigger the real back.
+        if (mBackNavigationInfo != null) {
+            IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+            if (mTriggerBack) {
+                dispatchOnBackInvoked(callback);
+            } else {
+                dispatchOnBackCancelled(callback);
+            }
+        }
+
+        finishBackNavigation();
+    }
+
+    /**
+     * This should be called after the whole back navigation is completed.
+     */
     @VisibleForTesting
     void finishBackNavigation() {
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
@@ -571,10 +573,10 @@
         mTriggerBack = false;
         mShouldStartOnNextMoveEvent = false;
         mTouchTracker.reset();
+        mActiveCallback = null;
         if (backNavigationInfo == null) {
             return;
         }
-        stopTransition();
         if (mBackAnimationFinishedCallback != null) {
             try {
                 mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
@@ -586,19 +588,6 @@
         backNavigationInfo.onBackNavigationFinished(triggerBack);
     }
 
-    private void startTransition() {
-        if (mTransitionInProgress) {
-            return;
-        }
-        mTransitionInProgress = true;
-        mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
-    }
-
-    void stopTransition() {
-        mShellExecutor.removeCallbacks(mResetTransitionRunnable);
-        mTransitionInProgress = false;
-    }
-
     private void createAdapter() {
         IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
             @Override
@@ -624,20 +613,18 @@
                     ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
                     runner.startAnimation(apps, wallpapers, nonApps,
                             BackAnimationController.this::onBackAnimationFinished);
+
                     if (apps.length >= 1) {
-                        final int backType = mBackNavigationInfo.getType();
-                        IOnBackInvokedCallback targetCallback = mAnimationDefinition.get(backType)
-                                .getCallback();
                         dispatchOnBackStarted(
-                                targetCallback, mTouchTracker.createStartEvent(apps[0]));
+                                mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
                     }
 
                     if (!mBackGestureStarted) {
                         // if the down -> up gesture happened before animation start, we have to
                         // trigger the uninterruptible transition to finish the back animation.
-                        final BackEvent backFinish = mTouchTracker.createProgressEvent(1);
-                        startTransition();
-                        runner.consumeIfGestureFinished(backFinish);
+                        final BackEvent backFinish = mTouchTracker.createProgressEvent();
+                        dispatchOnBackProgressed(mActiveCallback, backFinish);
+                        startPostCommitAnimation();
                     }
                 });
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index c53fcfc..d70b8f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -18,12 +18,12 @@
 
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 
+import android.annotation.NonNull;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
-import android.window.BackEvent;
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
@@ -38,11 +38,11 @@
     private final IOnBackInvokedCallback mCallback;
     private final IRemoteAnimationRunner mRunner;
 
-    private boolean mTriggerBack;
     // Whether we are waiting to receive onAnimationStart
     private boolean mWaitingAnimation;
 
-    BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
+    BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
+            @NonNull IRemoteAnimationRunner runner) {
         mCallback = callback;
         mRunner = runner;
     }
@@ -83,25 +83,7 @@
         mWaitingAnimation = true;
     }
 
-    boolean onGestureFinished(boolean triggerBack) {
-        if (mWaitingAnimation) {
-            mTriggerBack = triggerBack;
-            return true;
-        }
-        return false;
-    }
-
-    void consumeIfGestureFinished(final BackEvent backFinish) {
-        Log.d(TAG, "Start transition due to gesture is finished");
-        try {
-            mCallback.onBackProgressed(backFinish);
-            if (mTriggerBack) {
-                mCallback.onBackInvoked();
-            } else {
-                mCallback.onBackCancelled();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "dispatch error: ", e);
-        }
+    boolean isWaitingAnimation() {
+        return mWaitingAnimation;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a400555..1fd91de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -709,7 +709,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(mBroadcastReceiver, filter);
+        mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
     }
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
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 bb7c4134..d9b4f47 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
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -38,6 +39,7 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManagerGlobal;
 
 import androidx.annotation.VisibleForTesting;
@@ -112,7 +114,7 @@
         }
         if (mDisplayController.getDisplayLayout(displayId).rotation()
                 != pd.mRotation && isImeShowing(displayId)) {
-            pd.startAnimation(true, false /* forceRestart */);
+            pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
         }
     }
 
@@ -244,7 +246,7 @@
             mInsetsState.set(insetsState, true /* copySources */);
             if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                startAnimation(mImeShowing, true /* forceRestart */);
+                startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
             }
         }
 
@@ -280,7 +282,7 @@
                         !haveSameLeash(mImeSourceControl, imeSourceControl);
                 if (mAnimation != null) {
                     if (positionChanged) {
-                        startAnimation(mImeShowing, true /* forceRestart */);
+                        startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
                     }
                 } else {
                     if (leashChanged) {
@@ -312,21 +314,23 @@
         }
 
         @Override
-        public void showInsets(int types, boolean fromIme) {
+        public void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            startAnimation(true /* show */, false /* forceRestart */);
+            startAnimation(true /* show */, false /* forceRestart */, statsToken);
         }
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             if ((types & WindowInsets.Type.ime()) == 0) {
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            startAnimation(false /* show */, false /* forceRestart */);
+            startAnimation(false /* show */, false /* forceRestart */, statsToken);
         }
 
         @Override
@@ -367,9 +371,11 @@
                     .navBarFrameHeight();
         }
 
-        private void startAnimation(final boolean show, final boolean forceRestart) {
+        private void startAnimation(final boolean show, final boolean forceRestart,
+                @Nullable ImeTracker.Token statsToken) {
             final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
             if (imeSource == null || mImeSourceControl == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             final Rect newFrame = imeSource.getFrame();
@@ -390,8 +396,9 @@
                         + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
                         : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
             }
-            if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)
+            if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
                     || (mAnimationDirection == DIRECTION_HIDE && !show)) {
+                ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
             boolean seek = false;
@@ -435,8 +442,11 @@
                 mTransactionPool.release(t);
             });
             mAnimation.setInterpolator(INTERPOLATOR);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
             mAnimation.addListener(new AnimatorListenerAdapter() {
                 private boolean mCancelled = false;
+                @Nullable
+                private final ImeTracker.Token mStatsToken = statsToken;
 
                 @Override
                 public void onAnimationStart(Animator animation) {
@@ -455,6 +465,8 @@
                             : 1.f;
                     t.setAlpha(mImeSourceControl.getLeash(), alpha);
                     if (mAnimationDirection == DIRECTION_SHOW) {
+                        ImeTracker.get().onProgress(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.show(mImeSourceControl.getLeash());
                     }
                     t.apply();
@@ -476,8 +488,16 @@
                     }
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
+                        ImeTracker.get().onProgress(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                         t.hide(mImeSourceControl.getLeash());
                         removeImeSurface();
+                        ImeTracker.get().onHidden(mStatsToken);
+                    } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
+                        ImeTracker.get().onShown(mStatsToken);
+                    } else if (mCancelled) {
+                        ImeTracker.get().onCancelled(mStatsToken,
+                                ImeTracker.PHASE_WM_ANIMATION_RUNNING);
                     }
                     t.apply();
                     mTransactionPool.release(t);
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 8d4a09d..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
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.common;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -25,6 +26,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.annotation.BinderThread;
 
@@ -156,23 +158,29 @@
             }
         }
 
-        private void showInsets(int types, boolean fromIme) {
+        private void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
-                listener.showInsets(types, fromIme);
+                listener.showInsets(types, fromIme, statsToken);
             }
         }
 
-        private void hideInsets(int types, boolean fromIme) {
+        private void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
             if (listeners == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
                 return;
             }
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
             for (OnInsetsChangedListener listener : listeners) {
-                listener.hideInsets(types, fromIme);
+                listener.hideInsets(types, fromIme, statsToken);
             }
         }
 
@@ -214,16 +222,18 @@
             }
 
             @Override
-            public void showInsets(int types, boolean fromIme) throws RemoteException {
+            public void showInsets(@InsetsType int types, boolean fromIme,
+                    @Nullable ImeTracker.Token statsToken) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.showInsets(types, fromIme);
+                    PerDisplay.this.showInsets(types, fromIme, statsToken);
                 });
             }
 
             @Override
-            public void hideInsets(int types, boolean fromIme) throws RemoteException {
+            public void hideInsets(@InsetsType int types, boolean fromIme,
+                    @Nullable ImeTracker.Token statsToken) throws RemoteException {
                 mMainExecutor.execute(() -> {
-                    PerDisplay.this.hideInsets(types, fromIme);
+                    PerDisplay.this.hideInsets(types, fromIme, statsToken);
                 });
             }
         }
@@ -263,15 +273,21 @@
          *
          * @param types {@link InsetsType} to show
          * @param fromIme true if this request originated from IME (InputMethodService).
+         * @param statsToken the token tracking the current IME show request
+         *                   or {@code null} otherwise.
          */
-        default void showInsets(@InsetsType int types, boolean fromIme) {}
+        default void showInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {}
 
         /**
          * Called when a set of insets source window should be hidden by policy.
          *
          * @param types {@link InsetsType} to hide
          * @param fromIme true if this request originated from IME (InputMethodService).
+         * @param statsToken the token tracking the current IME hide request
+         *                   or {@code null} otherwise.
          */
-        default void hideInsets(@InsetsType int types, boolean fromIme) {}
+        default void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e270edb..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Region;
@@ -46,6 +47,7 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -351,10 +353,10 @@
                 InsetsSourceControl[] activeControls) {}
 
         @Override
-        public void showInsets(int types, boolean fromIme) {}
+        public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {}
+        public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {}
 
         @Override
         public void moved(int newX, int newY) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 839edc8..3de1045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -601,7 +601,7 @@
         animator.start();
     }
 
-    /** Swich both surface position with animation. */
+    /** Switch both surface position with animation. */
     public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
             SurfaceControl leash2, Consumer<Rect> finishCallback) {
         final boolean isLandscape = isLandscape();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 34ff6d8..abc4024 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -16,8 +16,11 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -151,21 +154,18 @@
 
         int displayId = mContext.getDisplayId();
 
+        ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        // Reset freeform windowing mode that is set per task level (tasks should inherit
-        // container value)
-        wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
-                true /* transfer */);
-        int targetWindowingMode;
+        // Reset freeform windowing mode that is set per task level so tasks inherit it
+        clearFreeformForStandardTasks(runningTasks, wct);
         if (active) {
-            targetWindowingMode = WINDOWING_MODE_FREEFORM;
+            moveHomeBehindVisibleTasks(runningTasks, wct);
+            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
         } else {
-            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
-            // Clear any resized bounds
-            wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
-                    true /* transfer */);
+            clearBoundsForStandardTasks(runningTasks, wct);
+            setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
         }
-        prepareWindowingModeChange(wct, displayId, targetWindowingMode);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
         } else {
@@ -173,17 +173,69 @@
         }
     }
 
-    private void prepareWindowingModeChange(WindowContainerTransaction wct,
-            int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
-        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
-                .getDisplayAreaInfo(displayId);
+    private WindowContainerTransaction clearBoundsForStandardTasks(
+            ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+                ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+                        taskInfo.token, taskInfo);
+                wct.setBounds(taskInfo.token, null);
+            }
+        }
+        return wct;
+    }
+
+    private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+            WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+                ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+                        taskInfo);
+                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+            }
+        }
+    }
+
+    private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+            WindowContainerTransaction wct) {
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+        RunningTaskInfo homeTask = null;
+        ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+        for (RunningTaskInfo taskInfo : runningTasks) {
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+                homeTask = taskInfo;
+            } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+                    && taskInfo.isVisible()) {
+                visibleTasks.add(taskInfo);
+            }
+        }
+        if (homeTask == null) {
+            ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+        } else {
+            ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+                    visibleTasks.size());
+            wct.reorder(homeTask.getToken(), true /* onTop */);
+            for (RunningTaskInfo task : visibleTasks) {
+                wct.reorder(task.getToken(), true /* onTop */);
+            }
+        }
+    }
+
+    private void setDisplayAreaWindowingMode(int displayId,
+            @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+                displayId);
         if (displayAreaInfo == null) {
             ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                     "unable to update windowing mode for display %d display not found", displayId);
             return;
         }
 
-        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+        ProtoLog.v(WM_SHELL_DESKTOP_MODE,
                 "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
                 displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
                 windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0e..5533ad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@
     /**
      * Starts a pair of intent and task in one transition.
      */
-    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
-            in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+    oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+            in Bundle options2, int sidePosition, float splitRatio,
             in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
 
     /**
@@ -108,9 +108,8 @@
      * Starts a pair of intent and task using legacy transition system.
      */
     oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
-            in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
-            int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
-            in InstanceId instanceId) = 12;
+            in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
 
     /**
      * Starts a pair of shortcut and task using legacy transition system.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b83..cdc8cdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+    private final String[] mMultiInstancesComponents;
 
-    private StageCoordinator mStageCoordinator;
+    @VisibleForTesting
+    StageCoordinator mStageCoordinator;
+
     // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
     // outside the bounds of the roots by being reparented into a higher level fullscreen container
     private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@
         if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
             shellInit.addInitCallback(this::onInit, this);
         }
+
+        // TODO(255224696): Remove the config once having a way for client apps to opt-in
+        //                  multi-instances split.
+        mMultiInstancesComponents = mContext.getResources()
+                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+    }
+
+    @VisibleForTesting
+    SplitScreenController(Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            ShellController shellController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            SyncTransactionQueue syncQueue,
+            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+            DisplayController displayController,
+            DisplayImeController displayImeController,
+            DisplayInsetsController displayInsetsController,
+            DragAndDropController dragAndDropController,
+            Transitions transitions,
+            TransactionPool transactionPool,
+            IconProvider iconProvider,
+            RecentTasksController recentTasks,
+            ShellExecutor mainExecutor,
+            StageCoordinator stageCoordinator) {
+        mShellCommandHandler = shellCommandHandler;
+        mShellController = shellController;
+        mTaskOrganizer = shellTaskOrganizer;
+        mSyncQueue = syncQueue;
+        mContext = context;
+        mRootTDAOrganizer = rootTDAOrganizer;
+        mMainExecutor = mainExecutor;
+        mDisplayController = displayController;
+        mDisplayImeController = displayImeController;
+        mDisplayInsetsController = displayInsetsController;
+        mDragAndDropController = dragAndDropController;
+        mTransitions = transitions;
+        mTransactionPool = transactionPool;
+        mIconProvider = iconProvider;
+        mRecentTasksOptional = Optional.of(recentTasks);
+        mStageCoordinator = stageCoordinator;
+        mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+        shellInit.addInitCallback(this::onInit, this);
+        mMultiInstancesComponents = mContext.getResources()
+                .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
     }
 
     public SplitScreen asSplitScreen() {
@@ -471,72 +521,116 @@
         startIntent(intent, fillInIntent, position, options);
     }
 
+    private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+            @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
+        Intent fillInIntent = null;
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+            fillInIntent = new Intent();
+            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+        mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+                options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+    }
+
+    private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+            int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+            float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        Intent fillInIntent = null;
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+            fillInIntent = new Intent();
+            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+        mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+                options2, splitPosition, splitRatio, remoteTransition, instanceId);
+    }
+
     @Override
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options) {
-        if (fillInIntent == null) {
-            fillInIntent = new Intent();
-        }
-        // Flag this as a no-user-action launch to prevent sending user leaving event to the
-        // current top activity since it's going to be put into another side of the split. This
-        // prevents the current top activity from going into pip mode due to user leaving event.
+        // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+        // top activity since it's going to be put into another side of the split. This prevents the
+        // current top activity from going into pip mode due to user leaving event.
+        if (fillInIntent == null) fillInIntent = new Intent();
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
-        // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
-        // split and there is no reusable background task.
-        if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
-            final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
-                    ? mRecentTasksOptional.get().findTaskInBackground(
-                            intent.getIntent().getComponent())
-                    : null;
-            if (taskInfo != null) {
-                startTask(taskInfo.taskId, position, options);
+        if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+            final ComponentName launching = intent.getIntent().getComponent();
+            if (supportMultiInstancesSplit(launching)) {
+                // To prevent accumulating large number of instances in the background, reuse task
+                // in the background with priority.
+                final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+                        .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+                        .orElse(null);
+                if (taskInfo != null) {
+                    startTask(taskInfo.taskId, position, options);
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                            "Start task in background");
+                    return;
+                }
+
+                // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+                // the split and there is no reusable background task.
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else if (isSplitScreenVisible()) {
+                mStageCoordinator.switchSplitPosition("startIntent");
                 return;
             }
-            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
         }
 
-        if (!ENABLE_SHELL_TRANSITIONS) {
-            mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
-            return;
-        }
         mStageCoordinator.startIntent(intent, fillInIntent, position, options);
     }
 
     /** Returns {@code true} if it's launching the same component on both sides of the split. */
-    @VisibleForTesting
-    boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
-        if (startIntent == null) {
-            return false;
-        }
+    private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+            @SplitPosition int position, int taskId) {
+        if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
 
-        final ComponentName launchingActivity = startIntent.getComponent();
-        if (launchingActivity == null) {
-            return false;
-        }
+        final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+        if (launchingActivity == null) return false;
 
-        if (isSplitScreenVisible()) {
-            // To prevent users from constantly dropping the same app to the same side resulting in
-            // a large number of instances in the background.
-            final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
-            final ComponentName targetActivity = targetTaskInfo != null
-                    ? targetTaskInfo.baseIntent.getComponent() : null;
-            if (Objects.equals(launchingActivity, targetActivity)) {
-                return false;
+        if (taskId != INVALID_TASK_ID) {
+            final ActivityManager.RunningTaskInfo taskInfo =
+                    mTaskOrganizer.getRunningTaskInfo(taskId);
+            if (taskInfo != null) {
+                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
             }
-
-            // Allow users to start a new instance the same to adjacent side.
-            final ActivityManager.RunningTaskInfo pairedTaskInfo =
-                    getTaskInfo(SplitLayout.reversePosition(position));
-            final ComponentName pairedActivity = pairedTaskInfo != null
-                    ? pairedTaskInfo.baseIntent.getComponent() : null;
-            return Objects.equals(launchingActivity, pairedActivity);
+            return false;
         }
 
-        final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
-        if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
-            return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+        if (!isSplitScreenVisible()) {
+            // Split screen is not yet activated, check if the current top running task is valid to
+            // split together.
+            final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+            if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+                return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+            }
+            return false;
+        }
+
+        // Compare to the adjacent side of the split to determine if this is launching the same
+        // component adjacently.
+        final ActivityManager.RunningTaskInfo pairedTaskInfo =
+                getTaskInfo(SplitLayout.reversePosition(position));
+        final ComponentName pairedActivity = pairedTaskInfo != null
+                ? pairedTaskInfo.baseIntent.getComponent() : null;
+        return Objects.equals(launchingActivity, pairedActivity);
+    }
+
+    @VisibleForTesting
+    /** Returns {@code true} if the component supports multi-instances split. */
+    boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+        if (launching == null) return false;
+
+        final String componentName = launching.flattenToString();
+        for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+            if (mMultiInstancesComponents[i].equals(componentName)) {
+                return true;
+            }
         }
 
         return false;
@@ -839,14 +933,13 @@
 
         @Override
         public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
-                Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
-                int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
-                InstanceId instanceId) {
+                Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+                RemoteAnimationAdapter adapter, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startIntentAndTaskWithLegacyTransition", (controller) ->
-                            controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
-                                    pendingIntent, fillInIntent, options1, taskId, options2,
-                                    splitPosition, splitRatio, adapter, instanceId));
+                            controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+                                    options1, taskId, options2, splitPosition, splitRatio, adapter,
+                                    instanceId));
         }
 
         @Override
@@ -872,14 +965,13 @@
         }
 
         @Override
-        public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
-                @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-                @SplitPosition int splitPosition, float splitRatio,
-                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+        public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+                int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+                float splitRatio, @Nullable RemoteTransition remoteTransition,
+                InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
-                    (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
-                            fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
-                            remoteTransition, instanceId));
+                    (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+                            options2, splitPosition, splitRatio, remoteTransition, instanceId));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e888c6f..c2ab7ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,7 +24,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@
     /** Launches an activity into split. */
     void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            startIntentLegacy(intent, fillInIntent, position, options);
+            return;
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@
         prepareEnterSplitScreen(wct, null /* taskInfo */, position);
 
         mSplitTransitions.startEnterTransition(transitType, wct, null, this,
-                aborted -> {
-                    // Switch the split position if launching as MULTIPLE_TASK failed.
-                    if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                        setSideStagePositionAnimated(
-                                SplitLayout.reversePosition(mSideStagePosition));
-                    }
-                } /* consumedCallback */,
+                null /* consumedCallback */,
                 (finishWct, finishT) -> {
                     if (!evictWct.isEmpty()) {
                         finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@
 
     /** Launches an activity into split by legacy transition. */
     void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
-            @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+            @SplitPosition int position, @Nullable Bundle options) {
         final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         prepareEvictChildTasks(position, evictWct);
 
@@ -473,12 +471,6 @@
                                 exitSplitScreen(mMainStage.getChildCount() == 0
                                         ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
                         mSplitUnsupportedToast.show();
-                    } else {
-                        // Switch the split position if launching as MULTIPLE_TASK failed.
-                        if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                            setSideStagePosition(SplitLayout.reversePosition(
-                                    getSideStagePosition()), null);
-                        }
                     }
 
                     // Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@
         mSideStage.evictInvisibleChildren(wct);
     }
 
-    Bundle resolveStartStage(@StageType int stage,
-            @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
-            @androidx.annotation.Nullable WindowContainerTransaction wct) {
+    Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+            @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
         switch (stage) {
             case STAGE_TYPE_UNDEFINED: {
                 if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@
                 : mMainStage.getTopVisibleChildTaskId();
     }
 
-    void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
-        if (mSideStagePosition == sideStagePosition) return;
-        SurfaceControl.Transaction t = mTransactionPool.acquire();
+    void switchSplitPosition(String reason) {
+        final SurfaceControl.Transaction t = mTransactionPool.acquire();
         mTempRect1.setEmpty();
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@
                         va.start();
                     });
                 });
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+                mSplitLayout.isLandscape());
     }
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1617,10 +1612,7 @@
 
     @Override
     public void onDoubleTappedDivider() {
-        setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
-        mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
-                getSideStagePosition(), mSideStage.getTopChildTaskUid(),
-                mSplitLayout.isLandscape());
+        switchSplitPosition("double tap");
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 2d6e8f5..08913c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -13,6 +13,8 @@
         <option name="run-command" value="cmd window tracing level all" />
         <!-- set WM tracing to frame (avoid incomplete states) -->
         <option name="run-command" value="cmd window tracing frame" />
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
         <!-- ensure lock screen mode is swipe -->
         <option name="run-command" value="locksettings set-disabled false" />
         <!-- restart launcher to activate TAPL -->
@@ -34,4 +36,4 @@
         <option name="collect-on-run-ended-only" value="true" />
         <option name="clean-up" value="true" />
     </metrics_collector>
-</configuration>
\ No newline at end of file
+</configuration>
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 2bce8e45..9533b91 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
@@ -24,6 +24,8 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.EdgeExtensionComponentMatcher
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -49,6 +51,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
     private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+    private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+    private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -159,8 +163,18 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        testSpec.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                ignoreLayers = listOf(
+                    ComponentNameMatcher.SPLASH_SCREEN,
+                    ComponentNameMatcher.SNAPSHOT,
+                    ComponentNameMatcher.IME_SNAPSHOT,
+                    EdgeExtensionComponentMatcher(),
+                    MagnifierLayer,
+                    PopupWindowLayer))
+        }
+    }
 
     /** {@inheritDoc} */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5..081c8ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
 
 package com.android.wm.shell;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
-import android.app.WindowConfiguration;
 import android.content.LocusId;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -61,8 +53,6 @@
 import android.window.ITaskOrganizerController;
 import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@
         verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
     }
 
-    @Test
-    public void testPrepareClearBoundsForStandardTasks() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        MockToken otherDisplayToken = new MockToken();
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
-                otherDisplayToken);
-        otherDisplayTask.displayId = 2;
-        mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
-        assertEquals(wct.getChanges().size(), 2);
-        Change boundsChange1 = wct.getChanges().get(token1.binder());
-        assertNotNull(boundsChange1);
-        assertNotEquals(
-                (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
-        assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
-        Change boundsChange2 = wct.getChanges().get(token2.binder());
-        assertNotNull(boundsChange2);
-        assertNotEquals(
-                (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
-        assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
-    }
-
-    @Test
-    public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
-        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
-        // Only clear bounds for task1
-        assertEquals(1, wct.getChanges().size());
-        assertNotNull(wct.getChanges().get(token1.binder()));
-    }
-
-    @Test
-    public void testPrepareClearFreeformForStandardTasks() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        MockToken otherDisplayToken = new MockToken();
-        RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
-                otherDisplayToken);
-        otherDisplayTask.displayId = 2;
-        mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
-        // Only task with freeform windowing mode and the right display should be updated
-        assertEquals(wct.getChanges().size(), 1);
-        Change wmModeChange1 = wct.getChanges().get(token1.binder());
-        assertNotNull(wmModeChange1);
-        assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
-    }
-
-    @Test
-    public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
-        MockToken token1 = new MockToken();
-        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
-        mOrganizer.onTaskAppeared(task1, null);
-
-        MockToken token2 = new MockToken();
-        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
-        task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
-        mOrganizer.onTaskAppeared(task2, null);
-
-        WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
-        // Only clear freeform for task1
-        assertEquals(1, wct.getChanges().size());
-        assertNotNull(wct.getChanges().get(token1.binder()));
-    }
-
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         return taskInfo;
     }
-
-    private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
-        RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
-        taskInfo.displayId = 1;
-        taskInfo.token = token.token();
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-        return taskInfo;
-    }
-
-    private static class MockToken {
-        private final WindowContainerToken mToken;
-        private final IBinder mBinder;
-
-        MockToken() {
-            mToken = mock(WindowContainerToken.class);
-            mBinder = mock(IBinder.class);
-            when(mToken.asBinder()).thenReturn(mBinder);
-        }
-
-        WindowContainerToken token() {
-            return mToken;
-        }
-
-        IBinder binder() {
-            return mBinder;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 7eccbf4..b603e03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -93,7 +94,10 @@
     private IActivityTaskManager mActivityTaskManager;
 
     @Mock
-    private IOnBackInvokedCallback mIOnBackInvokedCallback;
+    private IOnBackInvokedCallback mAppCallback;
+
+    @Mock
+    private IOnBackInvokedCallback mAnimatorCallback;
 
     @Mock
     private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@@ -105,8 +109,6 @@
     private ShellController mShellController;
 
     private BackAnimationController mController;
-
-    private int mEventTime = 0;
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
@@ -126,16 +128,15 @@
                 mContentResolver);
         mController.setEnableUAnimation(true);
         mShellInit.init();
-        mEventTime = 0;
         mShellExecutor.flushAll();
     }
 
-    private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
+    private void createNavigationInfo(int backType, boolean enableAnimation) {
         BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
                 .setType(backType)
                 .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
-                .setOnBackInvokedCallback(onBackInvokedCallback)
-                .setPrepareRemoteAnimation(true);
+                .setOnBackInvokedCallback(mAppCallback)
+                .setPrepareRemoteAnimation(enableAnimation);
 
         createNavigationInfo(builder);
     }
@@ -176,26 +177,47 @@
     }
 
     @Test
-    public void verifyAnimationFinishes() {
-        RemoteAnimationTarget animationTarget = createAnimationTarget();
-        boolean[] backNavigationDone = new boolean[]{false};
-        boolean[] triggerBack = new boolean[]{false};
-        createNavigationInfo(new BackNavigationInfo.Builder()
-                .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
-                .setOnBackNavigationDone(
-                        new RemoteCallback(result -> {
-                            backNavigationDone[0] = true;
-                            triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
-                        })));
-        triggerBackGesture();
-        assertTrue("Navigation Done callback not called", backNavigationDone[0]);
-        assertTrue("TriggerBack should have been true", triggerBack[0]);
+    public void verifyNavigationFinishes() throws RemoteException {
+        final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                BackNavigationInfo.TYPE_CROSS_TASK,
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                BackNavigationInfo.TYPE_DIALOG_CLOSE,
+                BackNavigationInfo.TYPE_CALLBACK };
+
+        for (int type: testTypes) {
+            registerAnimation(type);
+        }
+
+        for (int type: testTypes) {
+            boolean[] backNavigationDone = new boolean[]{false};
+            boolean[] triggerBack = new boolean[]{false};
+
+            createNavigationInfo(new BackNavigationInfo.Builder()
+                    .setType(type)
+                    .setOnBackInvokedCallback(mAppCallback)
+                    .setPrepareRemoteAnimation(true)
+                    .setOnBackNavigationDone(
+                            new RemoteCallback(result -> {
+                                backNavigationDone[0] = true;
+                                triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
+                            })));
+            triggerBackGesture();
+            simulateRemoteAnimationStart(type);
+            simulateRemoteAnimationFinished();
+            mShellExecutor.flushAll();
+
+            assertTrue("Navigation Done callback not called for "
+                    + BackNavigationInfo.typeToString(type), backNavigationDone[0]);
+            assertTrue("TriggerBack should have been true", triggerBack[0]);
+        }
     }
 
+
+
     @Test
     public void backToHome_dispatchesEvents() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
 
@@ -203,14 +225,16 @@
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+
+        verify(mAnimatorCallback).onBackStarted(any(BackEvent.class));
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
-        verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class));
+        ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+        verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verify(mIOnBackInvokedCallback).onBackInvoked();
+        verify(mAnimatorCallback).onBackInvoked();
     }
 
     @Test
@@ -223,97 +247,94 @@
                 mActivityTaskManager, mContext,
                 mContentResolver);
         shellInit.init();
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
         ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
 
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
 
         triggerBackGesture();
 
-        verify(appCallback, never()).onBackStarted(any(BackEvent.class));
-        verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
-        verify(appCallback, times(1)).onBackInvoked();
+        verify(mAppCallback, never()).onBackStarted(any());
+        verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(mAppCallback, times(1)).onBackInvoked();
 
-        verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class));
-        verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
-        verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+        verify(mAnimatorCallback, never()).onBackStarted(any());
+        verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(mAnimatorCallback, never()).onBackInvoked();
         verify(mBackAnimationRunner, never()).onAnimationStart(
                 anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void ignoresGesture_transitionInProgress() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
         // Check that back invocation is dispatched.
-        verify(mIOnBackInvokedCallback).onBackInvoked();
+        verify(mAnimatorCallback).onBackInvoked();
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
-        reset(mIOnBackInvokedCallback);
+        reset(mAnimatorCallback);
         reset(mBackAnimationRunner);
 
         // Verify that we prevent animation from restarting if another gestures happens before
         // the previous transition is finished.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
-        verifyNoMoreInteractions(mIOnBackInvokedCallback);
-        mController.onBackAnimationFinished();
-        // Pretend the transition handler called finishAnimation.
-        mController.finishBackNavigation();
+        verifyNoMoreInteractions(mAnimatorCallback);
+
+        // Finish back navigation.
+        simulateRemoteAnimationFinished();
 
         // Verify that more events from a rejected swipe cannot start animation.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verifyNoMoreInteractions(mIOnBackInvokedCallback);
+        verifyNoMoreInteractions(mAnimatorCallback);
 
         // Verify that we start accepting gestures again once transition finishes.
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
     }
 
     @Test
     public void acceptsGesture_transitionTimeout() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
+
+        // In case it is still running in animation.
+        doNothing().when(mAnimatorCallback).onBackInvoked();
 
         triggerBackGesture();
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        reset(mIOnBackInvokedCallback);
-
         // Simulate transition timeout.
         mShellExecutor.flushAll();
-        mController.onBackAnimationFinished();
-        // Pretend the transition handler called finishAnimation.
-        mController.finishBackNavigation();
+        reset(mAnimatorCallback);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
     }
 
-
     @Test
     public void cancelBackInvokeWhenLostFocus() throws RemoteException {
-        mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
+        registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
-        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+        createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true);
 
         doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         // Check that back start and progress is dispatched when first move.
         doMotionEvent(MotionEvent.ACTION_MOVE, 100);
 
         simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-        verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class));
+        verify(mAnimatorCallback).onBackStarted(any());
         verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
 
         // Check that back invocation is dispatched.
@@ -323,11 +344,11 @@
         IBinder token = mock(IBinder.class);
         mController.mFocusObserver.focusLost(token);
         mShellExecutor.flushAll();
-        verify(mIOnBackInvokedCallback).onBackCancelled();
+        verify(mAnimatorCallback).onBackCancelled();
 
         // No more back invoke.
         doMotionEvent(MotionEvent.ACTION_UP, 0);
-        verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+        verify(mAnimatorCallback, never()).onBackInvoked();
     }
 
     private void doMotionEvent(int actionDown, int coordinate) {
@@ -335,7 +356,6 @@
                 coordinate, coordinate,
                 actionDown,
                 BackEvent.EDGE_LEFT);
-        mEventTime += 10;
     }
 
     private void simulateRemoteAnimationStart(int type) throws RemoteException {
@@ -347,4 +367,14 @@
             mShellExecutor.flushAll();
         }
     }
+
+    private void simulateRemoteAnimationFinished() {
+        mController.onBackAnimationFinished();
+        mController.finishBackNavigation();
+    }
+
+    private void registerAnimation(int type) {
+        mController.registerAnimation(type,
+                new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index a6f19e7..40f2e88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -97,13 +97,13 @@
 
     @Test
     public void showInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.showInsets(ime(), true);
+        mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */);
         verifyZeroInteractions(mExecutor);
     }
 
     @Test
     public void hideInsets_schedulesNoWorkOnExecutor() {
-        mPerDisplay.hideInsets(ime(), true);
+        mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */);
         verifyZeroInteractions(mExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 39db328..956f1cd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.RemoteException;
 import android.util.SparseArray;
@@ -33,6 +34,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
 
@@ -111,8 +113,10 @@
                 WindowInsets.Type.defaultVisible());
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
-        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
-        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
+                null /* statsToken */);
+        mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
+                null /* statsToken */);
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -131,8 +135,10 @@
                 WindowInsets.Type.defaultVisible());
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
         mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
-        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
-        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
+                null /* statsToken */);
+        mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
+                null /* statsToken */);
         mExecutor.flushAll();
 
         assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -191,12 +197,12 @@
         }
 
         @Override
-        public void showInsets(int types, boolean fromIme) {
+        public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
             showInsetsCount++;
         }
 
         @Override
-        public void hideInsets(int types, boolean fromIme) {
+        public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
             hideInsetsCount++;
         }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 79b520c..89bafcb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,10 +16,13 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -30,13 +33,14 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -68,6 +72,9 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +90,7 @@
     @Mock
     private Handler mMockHandler;
     @Mock
-    private Transitions mMockTransitions;
-    private TestShellExecutor mExecutor;
-
+    private Transitions mTransitions;
     private DesktopModeController mController;
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
     private ShellInit mShellInit;
@@ -97,20 +102,19 @@
         when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
-        mExecutor = new TestShellExecutor();
 
         mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
         mController = new DesktopModeController(mContext, mShellInit, mShellController,
-                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
-                mDesktopModeTaskRepository, mMockHandler, mExecutor);
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
 
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
-                new WindowContainerTransaction());
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
 
         mShellInit.init();
         clearInvocations(mShellTaskOrganizer);
         clearInvocations(mRootTaskDisplayAreaOrganizer);
+        clearInvocations(mTransitions);
     }
 
     @After
@@ -124,113 +128,133 @@
     }
 
     @Test
-    public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
-        // Create a fake WCT to simulate setting task windowing mode to undefined
-        WindowContainerTransaction taskWct = new WindowContainerTransaction();
-        MockToken taskMockToken = new MockToken();
-        taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskWct);
+    public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
 
-        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
-        MockToken displayMockToken = new MockToken();
-        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
-                mContext.getDisplayId(), 0);
-        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
-                .thenReturn(displayAreaInfo);
-
-        // The test
         mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
 
-        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
-        // WCT should have 2 changes - clear task wm mode and set display wm mode
-        WindowContainerTransaction wct = arg.getValue();
-        assertThat(wct.getChanges()).hasSize(2);
-
-        // Verify executed WCT has a change for setting task windowing mode to undefined
-        Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
-        assertThat(taskWmModeChange).isNotNull();
-        assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
-        // Verify executed WCT has a change for setting display windowing mode to freeform
-        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(displayWmModeChange).isNotNull();
-        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+        // 1 change: Root TDA windowing mode
+        assertThat(wct.getChanges().size()).isEqualTo(1);
+        // Verify WCT has a change for setting windowing mode to freeform
+        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
     }
 
     @Test
-    public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
-        // Create a fake WCT to simulate setting task windowing mode to undefined
-        WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
-        MockToken taskWmMockToken = new MockToken();
-        taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
-        when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskWmWct);
+    public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+        DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
 
-        // Create a fake WCT to simulate clearing task bounds
-        WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
-        MockToken taskBoundsMockToken = new MockToken();
-        taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
-        when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
-                mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
-        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
-        MockToken displayMockToken = new MockToken();
-        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
-                mContext.getDisplayId(), 0);
-        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
-                .thenReturn(displayAreaInfo);
-
-        // The test
         mController.updateDesktopModeActive(false);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
 
-        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+        // 1 change: Root TDA windowing mode
+        assertThat(wct.getChanges().size()).isEqualTo(1);
+        // Verify WCT has a change for setting windowing mode to fullscreen
+        Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+    }
 
-        // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
-        WindowContainerTransaction wct = arg.getValue();
-        assertThat(wct.getChanges()).hasSize(3);
+    @Test
+    public void testDesktopModeEnabled_windowingModeCleared() {
+        createMockDisplayArea();
+        RunningTaskInfo freeformTask = createFreeformTask();
+        RunningTaskInfo fullscreenTask = createFullscreenTask();
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
 
-        // Verify executed WCT has a change for setting task windowing mode to undefined
-        Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
-        assertThat(taskWmMode).isNotNull();
-        assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+        mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
 
-        // Verify executed WCT has a change for clearing task bounds
-        Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
-        assertThat(bounds).isNotNull();
-        assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
-        assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+        // 2 changes: Root TDA windowing mode and 1 task
+        assertThat(wct.getChanges().size()).isEqualTo(2);
+        // No changes for tasks that are not standard or freeform
+        assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+        // Standard freeform task has windowing mode cleared
+        Change change = wct.getChanges().get(freeformTask.token.asBinder());
+        assertThat(change).isNotNull();
+        assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+    }
 
-        // Verify executed WCT has a change for setting display windowing mode to fullscreen
-        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
-        assertThat(displayWmModeChange).isNotNull();
-        assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+    @Test
+    public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+        createMockDisplayArea();
+        RunningTaskInfo freeformTask = createFreeformTask();
+        RunningTaskInfo fullscreenTask = createFullscreenTask();
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+        mController.updateDesktopModeActive(false);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+        // 3 changes: Root TDA windowing mode and 2 tasks
+        assertThat(wct.getChanges().size()).isEqualTo(3);
+        // No changes to home task
+        assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+        // Standard tasks have bounds cleared
+        assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+        assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+        // Freeform standard tasks have windowing mode cleared
+        assertThat(wct.getChanges().get(
+                freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+                WINDOWING_MODE_UNDEFINED);
+    }
+
+    @Test
+    public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+        createMockDisplayArea();
+        RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+        fullscreenTask1.isVisible = true;
+        RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+        fullscreenTask2.isVisible = false;
+        RunningTaskInfo homeTask = createHomeTask();
+        when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+                Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+        mController.updateDesktopModeActive(true);
+        WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+        // Check that there are hierarchy changes for home task and visible task
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+        // First show home task
+        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+        // Then visible task on top of it
+        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
     }
 
     @Test
     public void testShowDesktopApps() {
         // Set up two active tasks on desktop
-        mDesktopModeTaskRepository.addActiveTask(1);
-        mDesktopModeTaskRepository.addActiveTask(2);
-        MockToken token1 = new MockToken();
-        MockToken token2 = new MockToken();
-        ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
-                token1.token()).setLastActiveTime(100).build();
-        ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
-                token2.token()).setLastActiveTime(200).build();
-        when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
-        when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+        RunningTaskInfo freeformTask1 = createFreeformTask();
+        freeformTask1.lastActiveTime = 100;
+        RunningTaskInfo freeformTask2 = createFreeformTask();
+        freeformTask2.lastActiveTime = 200;
+        mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+                freeformTask1);
+        when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+                freeformTask2);
 
         // Run show desktop apps logic
         mController.showDesktopApps();
         ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
                 WindowContainerTransaction.class);
-        verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
+        } else {
+            verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+        }
         WindowContainerTransaction wct = wctCaptor.getValue();
 
         // Check wct has reorder calls
@@ -239,12 +263,12 @@
         // Task 2 has activity later, must be first
         WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(token2.binder());
+        assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
 
         // Task 1 should be second
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(token2.binder());
+        assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
     }
 
     @Test
@@ -266,7 +290,7 @@
 
     @Test
     public void testHandleTransitionRequest_notFreeform_returnsNull() {
-        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        RunningTaskInfo trigger = new RunningTaskInfo();
         trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         WindowContainerTransaction wct = mController.handleRequest(
                 new Binder(),
@@ -276,7 +300,7 @@
 
     @Test
     public void testHandleTransitionRequest_returnsWct() {
-        ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+        RunningTaskInfo trigger = new RunningTaskInfo();
         trigger.token = new MockToken().mToken;
         trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +309,57 @@
         assertThat(wct).isNotNull();
     }
 
+    private DisplayAreaInfo createMockDisplayArea() {
+        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+                mContext.getDisplayId(), 0);
+        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+                .thenReturn(displayAreaInfo);
+        return displayAreaInfo;
+    }
+
+    private RunningTaskInfo createFreeformTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private RunningTaskInfo createFullscreenTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private RunningTaskInfo createHomeTask() {
+        return new TestRunningTaskInfoBuilder()
+                .setToken(new MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_HOME)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLastActiveTime(100)
+                .build();
+    }
+
+    private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+        ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+        } else {
+            verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+        }
+        return arg.getValue();
+    }
+
+    private void assertThatBoundsCleared(Change change) {
+        assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+        assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+    }
+
     private static class MockToken {
         private final WindowContainerToken mToken;
         private final IBinder mBinder;
@@ -298,9 +373,5 @@
         WindowContainerToken token() {
             return mToken;
         }
-
-        IBinder binder() {
-            return mBinder;
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d3..38b75f8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 /**
  * Tests for {@link SplitScreenController}
  */
@@ -91,18 +99,21 @@
     @Mock Transitions mTransitions;
     @Mock TransactionPool mTransactionPool;
     @Mock IconProvider mIconProvider;
-    @Mock Optional<RecentTasksController> mRecentTasks;
+    @Mock StageCoordinator mStageCoordinator;
+    @Mock RecentTasksController mRecentTasks;
+    @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private SplitScreenController mSplitScreenController;
 
     @Before
     public void setup() {
+        assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
         MockitoAnnotations.initMocks(this);
         mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
-                mIconProvider, mRecentTasks, mMainExecutor));
+                mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
     }
 
     @Test
@@ -148,58 +159,100 @@
     }
 
     @Test
-    public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
-        doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
-        doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
-        // Verify launching the same activity returns true.
+    public void testStartIntent_appendsNoUserActionFlag() {
         Intent startIntent = createStartIntent("startActivity");
-        ActivityManager.RunningTaskInfo focusTaskInfo =
-                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
-        doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
-        assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
 
-        // Verify launching different activity returns false.
-        Intent diffIntent = createStartIntent("diffActivity");
-        focusTaskInfo =
-                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
-        doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+                eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+        assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+                mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
     }
 
     @Test
-    public void testShouldAddMultipleTaskFlag_inSplitScreen() {
-        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+    public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
         Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into focus task
+        ActivityManager.RunningTaskInfo focusTaskInfo =
+                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+                eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+        assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+                mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+    }
+
+    @Test
+    public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into focus task
+        ActivityManager.RunningTaskInfo focusTaskInfo =
+                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+        doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+        // Put the same component into a task in the background
+        ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+        doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+                isNull());
+    }
+
+    @Test
+    public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into another side of the split
+        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
         ActivityManager.RunningTaskInfo sameTaskInfo =
                 createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
-        Intent diffIntent = createStartIntent("diffActivity");
-        ActivityManager.RunningTaskInfo differentTaskInfo =
-                createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+        doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+                SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        // Put the same component into a task in the background
+        doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+                .findTaskInBackground(any());
 
-        // Verify launching the same activity return false.
-        doReturn(sameTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
 
-        // Verify launching the same activity as adjacent returns true.
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        doReturn(sameTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
-        assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+        verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+                isNull());
+    }
 
-        // Verify launching different activity from adjacent returns false.
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
-        doReturn(differentTaskInfo).when(mSplitScreenController)
-                .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
-        assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
-                startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+    @Test
+    public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+        doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        Intent startIntent = createStartIntent("startActivity");
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+        // Put the same component into another side of the split
+        doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+        ActivityManager.RunningTaskInfo sameTaskInfo =
+                createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+        doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+                SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+        mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+        verify(mStageCoordinator).switchSplitPosition(anyString());
     }
 
     private Intent createStartIntent(String activityName) {
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eb8d26a..b1f327c 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -211,6 +211,8 @@
         "tests/data/**/*.apk",
         "tests/data/**/*.arsc",
         "tests/data/**/*.idmap",
+        ":FrameworkResourcesSparseTestApp",
+        ":FrameworkResourcesNotSparseTestApp",
     ],
     test_suites: ["device-tests"],
 }
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d214e2d..c90ec19 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -71,62 +71,6 @@
   ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
 }
 
-TEST(LoadedArscTest, LoadSparseEntryApp) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
-                                                                   contents.length());
-  ASSERT_THAT(loaded_arsc, NotNull());
-
-  const LoadedPackage* package =
-      loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
-  ASSERT_THAT(package, NotNull());
-
-  const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
-  const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
-
-  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
-  ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
-  auto type = type_spec->type_entries[0];
-  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
-}
-
-TEST(LoadedArscTest, FindSparseEntryApp) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
-                                                                   contents.length());
-  ASSERT_THAT(loaded_arsc, NotNull());
-
-  const LoadedPackage* package =
-      loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_v26));
-  ASSERT_THAT(package, NotNull());
-
-  const uint8_t type_index = get_type_id(sparse::R::string::only_v26) - 1;
-  const uint16_t entry_index = get_entry_id(sparse::R::string::only_v26);
-
-  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
-  ASSERT_THAT(type_spec, NotNull());
-  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
-
-  // Ensure that AAPT2 sparsely encoded the v26 config as expected.
-  auto type_entry = std::find_if(
-    type_spec->type_entries.begin(), type_spec->type_entries.end(),
-    [](const TypeSpec::TypeEntry& x) { return x.config.sdkVersion == 26; });
-  ASSERT_NE(type_entry, type_spec->type_entries.end());
-  ASSERT_NE(type_entry->type->flags & ResTable_type::FLAG_SPARSE, 0);
-
-  // Test fetching a resource with only sparsely encoded configs by name.
-  auto id = package->FindEntryByName(u"string", u"only_v26");
-  ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_v26, 0));
-}
-
 TEST(LoadedArscTest, LoadSharedLibrary) {
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
@@ -404,4 +348,84 @@
 // sizeof(Res_value) might not be backwards compatible.
 // TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); }
 
+class LoadedArscParameterizedTest :
+    public testing::TestWithParam<std::string> {
+};
+
+TEST_P(LoadedArscParameterizedTest, LoadSparseEntryApp) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+                                                                   contents.length());
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+  ASSERT_THAT(package, NotNull());
+
+  const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+  const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+  auto type = type_spec->type_entries[0];
+  ASSERT_TRUE(LoadedPackage::GetEntry(type.type, entry_index).has_value());
+}
+
+TEST_P(LoadedArscParameterizedTest, FindSparseEntryApp) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(contents.data(),
+                                                                   contents.length());
+  ASSERT_THAT(loaded_arsc, NotNull());
+
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::string::only_land));
+  ASSERT_THAT(package, NotNull());
+
+  const uint8_t type_index = get_type_id(sparse::R::string::only_land) - 1;
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_entries.size(), Ge(1u));
+
+  // Type Entry with default orientation is not sparse encoded because the ratio of
+  // populated entries to total entries is above threshold.
+  // Only find out default locale because Soong build system will introduce pseudo
+  // locales for the apk generated at runtime.
+  auto type_entry_default = std::find_if(
+    type_spec->type_entries.begin(), type_spec->type_entries.end(),
+    [] (const TypeSpec::TypeEntry& x) { return x.config.orientation == 0 &&
+                                               x.config.locale == 0; });
+  ASSERT_NE(type_entry_default, type_spec->type_entries.end());
+  ASSERT_EQ(type_entry_default->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+  // Type Entry with land orientation is sparse encoded as expected.
+  // Only find out default locale because Soong build system will introduce pseudo
+  // locales for the apk generated at runtime.
+  auto type_entry_land = std::find_if(
+    type_spec->type_entries.begin(), type_spec->type_entries.end(),
+    [](const TypeSpec::TypeEntry& x) { return x.config.orientation ==
+                                              ResTable_config::ORIENTATION_LAND &&
+                                              x.config.locale == 0; });
+  ASSERT_NE(type_entry_land, type_spec->type_entries.end());
+  ASSERT_NE(type_entry_land->type->flags & ResTable_type::FLAG_SPARSE, 0);
+
+  // Test fetching a resource with only sparsely encoded configs by name.
+  auto id = package->FindEntryByName(u"string", u"only_land");
+  ASSERT_EQ(id.value(), fix_package_id(sparse::R::string::only_land, 0));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FrameWorkResourcesLoadedArscTests,
+        LoadedArscParameterizedTest,
+        ::testing::Values(
+          base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+          base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+        ));
+
 }  // namespace android
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
index 9aeb00c..fbf7098 100644
--- a/libs/androidfw/tests/ResTable_test.cpp
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
 
 #include <codecvt>
 #include <locale>
@@ -41,34 +42,6 @@
   ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
 }
 
-TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
-  std::string contents;
-  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
-                                      &contents));
-
-  ResTable table;
-  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
-
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  table.setParameters(&config);
-
-  String16 name(u"com.android.sparse:integer/foo_9");
-  uint32_t flags;
-  uint32_t resid =
-      table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
-  ASSERT_NE(0u, resid);
-
-  Res_value val;
-  ResTable_config selected_config;
-  ASSERT_GE(
-      table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
-      0);
-  EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
-  EXPECT_EQ(900u, val.data);
-}
-
 TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
@@ -476,4 +449,43 @@
   ASSERT_FALSE(invalid_pool->stringAt(invalid_val.data).has_value());
 }
 
+class ResTableParameterizedTest :
+    public testing::TestWithParam<std::string> {
+};
+
+TEST_P(ResTableParameterizedTest, ShouldLoadSparseEntriesSuccessfully) {
+  std::string contents;
+  ASSERT_TRUE(ReadFileFromZipToString(GetParam(), "resources.arsc", &contents));
+
+  ResTable table;
+  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
+
+  ResTable_config config;
+  memset(&config, 0, sizeof(config));
+  config.orientation = ResTable_config::ORIENTATION_LAND;
+  table.setParameters(&config);
+
+  String16 name(u"com.android.sparse:integer/foo_9");
+  uint32_t flags;
+  uint32_t resid =
+      table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
+  ASSERT_NE(0u, resid);
+
+  Res_value val;
+  ResTable_config selected_config;
+  ASSERT_GE(
+      table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
+      0);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+  EXPECT_EQ(900u, val.data);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        FrameWorkResourcesResTableTests,
+        ResTableParameterizedTest,
+        ::testing::Values(
+           base::GetExecutableDirectory() + "/tests/data/sparse/sparse.apk",
+           base::GetExecutableDirectory() + "/FrameworkResourcesSparseTestApp.apk"
+        ));
+
 }  // namespace android
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index c9b4ad8..fffeeb8 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -16,6 +16,7 @@
 
 #include "androidfw/AssetManager.h"
 #include "androidfw/ResourceTypes.h"
+#include "android-base/file.h"
 
 #include "BenchmarkHelpers.h"
 #include "data/sparse/R.h"
@@ -24,40 +25,74 @@
 
 namespace android {
 
+static void BM_SparseEntryGetResourceHelper(const std::vector<std::string>& paths,
+                    uint32_t resid, benchmark::State& state, void (*GetResourceBenchmarkFunc)(
+                    const std::vector<std::string>&, const ResTable_config*,
+                    uint32_t, benchmark::State&)){
+    ResTable_config config;
+    memset(&config, 0, sizeof(config));
+    config.orientation = ResTable_config::ORIENTATION_LAND;
+    GetResourceBenchmarkFunc(paths, &config, resid, state);
+}
+
 static void BM_SparseEntryGetResourceOldSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+                                    state, &GetResourceBenchmarkOld);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceOldNotSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmarkOld({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+   BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+                                   state, &GetResourceBenchmarkOld);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmark({GetTestDataPath() + "/sparse/sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/sparse.apk"}, resid,
+                                  state, &GetResourceBenchmark);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparse, Large, sparse::R::string::foo_999);
 
 static void BM_SparseEntryGetResourceNotSparse(benchmark::State& state, uint32_t resid) {
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
-  GetResourceBenchmark({GetTestDataPath() + "/sparse/not_sparse.apk"}, &config, resid, state);
+  BM_SparseEntryGetResourceHelper({GetTestDataPath() + "/sparse/not_sparse.apk"}, resid,
+                                  state, &GetResourceBenchmark);
 }
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Small, sparse::R::integer::foo_9);
 BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparse, Large, sparse::R::string::foo_999);
 
+static void BM_SparseEntryGetResourceOldSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceOldNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmarkOld);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceOldNotSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceSparseRuntime, Large, sparse::R::string::foo_999);
+
+static void BM_SparseEntryGetResourceNotSparseRuntime(benchmark::State& state, uint32_t resid) {
+  BM_SparseEntryGetResourceHelper({base::GetExecutableDirectory() +
+                                  "/FrameworkResourcesNotSparseTestApp.apk"}, resid, state,
+                                  &GetResourceBenchmark);
+}
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Small, sparse::R::integer::foo_9);
+BENCHMARK_CAPTURE(BM_SparseEntryGetResourceNotSparseRuntime, Large, sparse::R::string::foo_999);
+
 }  // namespace android
diff --git a/libs/androidfw/tests/data/sparse/Android.bp b/libs/androidfw/tests/data/sparse/Android.bp
new file mode 100644
index 0000000..0fed79e
--- /dev/null
+++ b/libs/androidfw/tests/data/sparse/Android.bp
@@ -0,0 +1,14 @@
+android_test_helper_app {
+    name: "FrameworkResourcesSparseTestApp",
+    sdk_version: "current",
+    min_sdk_version: "32",
+    aaptflags: [
+        "--enable-sparse-encoding",
+    ],
+}
+
+android_test_helper_app {
+    name: "FrameworkResourcesNotSparseTestApp",
+    sdk_version: "current",
+    min_sdk_version: "32",
+}
diff --git a/libs/androidfw/tests/data/sparse/AndroidManifest.xml b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
index 27911b6..9c23a72 100644
--- a/libs/androidfw/tests/data/sparse/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/sparse/AndroidManifest.xml
@@ -17,4 +17,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.sparse">
     <application />
+    <uses-sdk android:minSdkVersion="32" />
 </manifest>
diff --git a/libs/androidfw/tests/data/sparse/R.h b/libs/androidfw/tests/data/sparse/R.h
index 2492dbf..a66e1af 100644
--- a/libs/androidfw/tests/data/sparse/R.h
+++ b/libs/androidfw/tests/data/sparse/R.h
@@ -42,7 +42,7 @@
   struct string {
     enum : uint32_t {
       foo_999 = 0x7f0203e7,
-      only_v26 = 0x7f0203e8
+      only_land = 0x7f0203e8
     };
   };
 };
diff --git a/libs/androidfw/tests/data/sparse/gen_strings.sh b/libs/androidfw/tests/data/sparse/gen_strings.sh
index 4ea5468..114ecbb 100755
--- a/libs/androidfw/tests/data/sparse/gen_strings.sh
+++ b/libs/androidfw/tests/data/sparse/gen_strings.sh
@@ -1,20 +1,20 @@
 #!/bin/bash
 
 OUTPUT_default=res/values/strings.xml
-OUTPUT_v26=res/values-v26/strings.xml
+OUTPUT_land=res/values-land/strings.xml
 
 echo "<resources>" > $OUTPUT_default
-echo "<resources>" > $OUTPUT_v26
+echo "<resources>" > $OUTPUT_land
 for i in {0..999}
 do
     echo "  <string name=\"foo_$i\">$i</string>" >> $OUTPUT_default
     if [ "$(($i % 3))" -eq "0" ]
     then
-        echo "  <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_v26
+        echo "  <string name=\"foo_$i\">$(($i * 10))</string>" >> $OUTPUT_land
     fi
 done
 echo "</resources>" >> $OUTPUT_default
 
-echo "  <string name=\"only_v26\">only v26</string>" >> $OUTPUT_v26
-echo "</resources>" >> $OUTPUT_v26
+echo "  <string name=\"only_land\">only land</string>" >> $OUTPUT_land
+echo "</resources>" >> $OUTPUT_land
 
diff --git a/libs/androidfw/tests/data/sparse/not_sparse.apk b/libs/androidfw/tests/data/sparse/not_sparse.apk
index b08a621..4d4d4a8 100644
--- a/libs/androidfw/tests/data/sparse/not_sparse.apk
+++ b/libs/androidfw/tests/data/sparse/not_sparse.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
similarity index 99%
rename from libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/strings.xml
index d116087e..66222c3 100644
--- a/libs/androidfw/tests/data/sparse/res/values-v26/strings.xml
+++ b/libs/androidfw/tests/data/sparse/res/values-land/strings.xml
@@ -333,5 +333,5 @@
   <string name="foo_993">9930</string>
   <string name="foo_996">9960</string>
   <string name="foo_999">9990</string>
-  <string name="only_v26">only v26</string>
+  <string name="only_land">only land</string>
 </resources>
diff --git a/libs/androidfw/tests/data/sparse/res/values-v26/values.xml b/libs/androidfw/tests/data/sparse/res/values-land/values.xml
similarity index 100%
rename from libs/androidfw/tests/data/sparse/res/values-v26/values.xml
rename to libs/androidfw/tests/data/sparse/res/values-land/values.xml
diff --git a/libs/androidfw/tests/data/sparse/sparse.apk b/libs/androidfw/tests/data/sparse/sparse.apk
index 9fd01fb..0f2d75a 100644
--- a/libs/androidfw/tests/data/sparse/sparse.apk
+++ b/libs/androidfw/tests/data/sparse/sparse.apk
Binary files differ
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1bd12af..7e1bbe3 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -244,12 +244,9 @@
                 mCallback = null;
                 return;
             }
-            if (handler == null) {
-                handler = new Handler();
-            }
+            Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
             callback.mSession = this;
-            CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
-                    callback);
+            CallbackMessageHandler msgHandler = new CallbackMessageHandler(looper, callback);
             mCallback = msgHandler;
         }
     }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index e67ea7e..5d1e164 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -105,7 +105,7 @@
             loge("Unable to start the slice purchase application on the non-default data "
                     + "subscription: " + mSubId);
             SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
-                    intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB);
+                    intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
             finishAndRemoveTask();
             return;
         }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 5761b3c..c2ad2c560 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -173,7 +173,7 @@
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR)
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
                 && isPendingIntentValid(intent,
-                        SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB)
+                        SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
     }
 
@@ -204,8 +204,8 @@
             case SlicePurchaseController.EXTRA_INTENT_CANCELED: return "canceled";
             case SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
             case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
-            case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB:
-                return "not default data sub";
+            case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+                return "not default data subscription";
             case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
             default: {
                 loge("Unknown pending intent extra: " + extra);
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 4fb77d7..b8fd579 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,10 @@
 
 buildscript {
     ext {
-        spa_min_sdk = 21
-        spa_target_sdk = 33
-        jetpack_compose_version = '1.3.0'
+        BUILD_TOOLS_VERSION = "30.0.3"
+        MIN_SDK = 21
+        TARGET_SDK = 33
+        jetpack_compose_version = '1.4.0-alpha01'
         jetpack_compose_compiler_version = '1.3.2'
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index e04a9be..7868aff 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -21,12 +21,13 @@
 
 android {
     namespace 'com.android.settingslib.spa.gallery'
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
         applicationId "com.android.settingslib.spa.gallery"
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
         versionCode 1
         versionName "1.0"
     }
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 037b45e..2eaa73e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -24,6 +24,9 @@
     srcs: ["src/**/*.kt"],
 
     static_libs: [
+        "androidx.slice_slice-builders",
+        "androidx.slice_slice-core",
+        "androidx.slice_slice-view",
         "androidx.compose.material3_material3",
         "androidx.compose.material_material-icons-extended",
         "androidx.compose.runtime_runtime",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 7a20c747..4944784 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -21,11 +21,12 @@
 
 android {
     namespace 'com.android.settingslib.spa'
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
     }
 
     sourceSets {
@@ -55,6 +56,9 @@
 
 dependencies {
     api "androidx.appcompat:appcompat:1.7.0-alpha01"
+    api "androidx.slice:slice-builders:1.1.0-alpha02"
+    api "androidx.slice:slice-core:1.1.0-alpha02"
+    api "androidx.slice:slice-view:1.1.0-alpha02"
     api "androidx.compose.material3:material3:1.1.0-alpha01"
     api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
     api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
index 35b9c0f..3689e4e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
@@ -119,7 +119,7 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || entry.mutableStatus) continue
+            if (!entry.isAllowSearch || entry.hasMutableStatus) continue
             fetchStatusData(entry, cursor)
         }
         return cursor
@@ -129,7 +129,7 @@
         val entryRepository by spaEnvironment.entryRepository
         val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
         for (entry in entryRepository.getAllEntries()) {
-            if (!entry.isAllowSearch || !entry.mutableStatus) continue
+            if (!entry.isAllowSearch || !entry.hasMutableStatus) continue
             fetchStatusData(entry, cursor)
         }
         return cursor
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
new file mode 100644
index 0000000..8c038c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceBroadcastReceiver.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+class SpaSliceBroadcastReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context?, intent: Intent?) {
+        val sliceRepository = SpaEnvironmentFactory.instance.sliceDataRepository
+        val sliceUri = intent?.data ?: return
+        val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
+        sliceData.doAction()
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
new file mode 100644
index 0000000..d800e50
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSliceProvider.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework
+
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.Observer
+import androidx.slice.Slice
+import androidx.slice.SliceProvider
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+private const val TAG = "SpaSliceProvider"
+
+class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
+    private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
+        if (!SpaEnvironmentFactory.isReady()) return null
+        return SpaEnvironmentFactory.instance.sliceDataRepository.getOrBuildSliceData(sliceUri)
+    }
+
+    override fun onBindSlice(sliceUri: Uri): Slice? {
+        if (context == null) return null
+        Log.d(TAG, "onBindSlice: $sliceUri")
+        return getOrPutSliceData(sliceUri)?.value
+    }
+
+    override fun onSlicePinned(sliceUri: Uri) {
+        Log.d(TAG, "onSlicePinned: $sliceUri")
+        super.onSlicePinned(sliceUri)
+        val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+        runBlocking {
+            withContext(Dispatchers.Main) {
+                sliceLiveData.observeForever(this@SpaSliceProvider)
+            }
+        }
+    }
+
+    override fun onSliceUnpinned(sliceUri: Uri) {
+        Log.d(TAG, "onSliceUnpinned: $sliceUri")
+        super.onSliceUnpinned(sliceUri)
+        val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
+        runBlocking {
+            withContext(Dispatchers.Main) {
+                sliceLiveData.removeObserver(this@SpaSliceProvider)
+            }
+        }
+    }
+
+    override fun onChanged(slice: Slice?) {
+        val uri = slice?.uri ?: return
+        Log.d(TAG, "onChanged: $uri")
+        context?.contentResolver?.notifyChange(uri, null)
+    }
+
+    override fun onCreateSliceProvider(): Boolean {
+        Log.d(TAG, "onCreateSliceProvider")
+        return true
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
new file mode 100644
index 0000000..fc551a8
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/EntrySliceData.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.lifecycle.LiveData
+import androidx.slice.Slice
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+open class EntrySliceData : LiveData<Slice?>() {
+    private val asyncRunnerScope = CoroutineScope(Dispatchers.IO)
+    private var asyncRunnerJob: Job? = null
+    private var asyncActionJob: Job? = null
+    private var isActive = false
+
+    open suspend fun asyncRunner() {}
+
+    open suspend fun asyncAction() {}
+
+    override fun onActive() {
+        asyncRunnerJob?.cancel()
+        asyncRunnerJob = asyncRunnerScope.launch { asyncRunner() }
+        isActive = true
+    }
+
+    override fun onInactive() {
+        asyncRunnerJob?.cancel()
+        asyncRunnerJob = null
+        asyncActionJob?.cancel()
+        asyncActionJob = null
+        isActive = false
+    }
+
+    fun isActive(): Boolean {
+        return isActive
+    }
+
+    fun doAction() {
+        asyncActionJob?.cancel()
+        asyncActionJob = asyncRunnerScope.launch { asyncAction() }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 224fe1d..9ee7f9e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.net.Uri
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -39,6 +40,11 @@
 val LocalEntryDataProvider =
     compositionLocalOf<EntryData> { object : EntryData {} }
 
+typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit
+typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData?
+typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData?
+typealias SliceDataGetter = (sliceUri: Uri, arguments: Bundle?) -> EntrySliceData?
+
 /**
  * Defines data of a Settings entry.
  */
@@ -71,7 +77,10 @@
 
     // Indicate whether the status of entry is mutable.
     // If so, for instance, we'll reindex its status for search.
-    val mutableStatus: Boolean = false,
+    val hasMutableStatus: Boolean = false,
+
+    // Indicate whether the entry has SliceProvider support.
+    val hasSliceSupport: Boolean = false,
 
     /**
      * ========================================
@@ -83,13 +92,19 @@
      * API to get the status data of the entry, such as isDisabled / isSwitchOff.
      * Returns null if this entry do NOT have any status.
      */
-    private val statusDataImpl: (arguments: Bundle?) -> EntryStatusData? = { null },
+    private val statusDataImpl: StatusDataGetter = { null },
 
     /**
      * API to get Search indexing data for this entry, such as title / keyword.
      * Returns null if this entry do NOT support search.
      */
-    private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
+    private val searchDataImpl: SearchDataGetter = { null },
+
+    /**
+     * API to get Slice data of this entry. The Slice data is implemented as a LiveData,
+     * and is associated with the Slice's lifecycle (pin / unpin) by the framework.
+     */
+    private val sliceDataImpl: SliceDataGetter = { _: Uri, _: Bundle? -> null },
 
     /**
      * API to Render UI of this entry directly. For now, we use it in the internal injection, to
@@ -97,7 +112,7 @@
      * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
      * use each entries' UI rendering function in the page instead.
      */
-    private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
+    private val uiLayoutImpl: UiLayerRenderer = {},
 ) {
     fun containerPage(): SettingsPage {
         // The Container page of the entry, which is the from-page or
@@ -121,6 +136,10 @@
         return searchDataImpl(fullArgument(runtimeArguments))
     }
 
+    fun getSliceData(sliceUri: Uri, runtimeArguments: Bundle? = null): EntrySliceData? {
+        return sliceDataImpl(sliceUri, fullArgument(runtimeArguments))
+    }
+
     @Composable
     fun UiLayout(runtimeArguments: Bundle? = null) {
         CompositionLocalProvider(provideLocalEntryData()) {
@@ -152,12 +171,14 @@
     // Attributes
     private var isAllowSearch: Boolean = false
     private var isSearchDataDynamic: Boolean = false
-    private var mutableStatus: Boolean = false
+    private var hasMutableStatus: Boolean = false
+    private var hasSliceSupport: Boolean = false
 
     // Functions
-    private var statusDataFn: (arguments: Bundle?) -> EntryStatusData? = { null }
-    private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
-    private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
+    private var uiLayoutFn: UiLayerRenderer = { }
+    private var statusDataFn: StatusDataGetter = { null }
+    private var searchDataFn: SearchDataGetter = { null }
+    private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
 
     fun build(): SettingsEntry {
         return SettingsEntry(
@@ -173,11 +194,13 @@
             // attributes
             isAllowSearch = isAllowSearch,
             isSearchDataDynamic = isSearchDataDynamic,
-            mutableStatus = mutableStatus,
+            hasMutableStatus = hasMutableStatus,
+            hasSliceSupport = hasSliceSupport,
 
             // functions
             statusDataImpl = statusDataFn,
             searchDataImpl = searchDataFn,
+            sliceDataImpl = sliceDataFn,
             uiLayoutImpl = uiLayoutFn,
         )
     }
@@ -207,7 +230,7 @@
     }
 
     fun setHasMutableStatus(hasMutableStatus: Boolean): SettingsEntryBuilder {
-        this.mutableStatus = hasMutableStatus
+        this.hasMutableStatus = hasMutableStatus
         return this
     }
 
@@ -221,17 +244,23 @@
         return this
     }
 
-    fun setStatusDataFn(fn: (arguments: Bundle?) -> EntryStatusData?): SettingsEntryBuilder {
+    fun setStatusDataFn(fn: StatusDataGetter): SettingsEntryBuilder {
         this.statusDataFn = fn
         return this
     }
 
-    fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
+    fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
         this.searchDataFn = fn
         return this
     }
 
-    fun setUiLayoutFn(fn: @Composable (arguments: Bundle?) -> Unit): SettingsEntryBuilder {
+    fun setSliceDataFn(fn: SliceDataGetter): SettingsEntryBuilder {
+        this.sliceDataFn = fn
+        this.hasSliceSupport = true
+        return this
+    }
+
+    fun setUiLayoutFn(fn: UiLayerRenderer): SettingsEntryBuilder {
         this.uiLayoutFn = fn
         return this
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index bb287d1..a372bbd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -113,6 +113,12 @@
         )
     }
 
+    fun createBrowseIntent(entryId: String? = null): Intent? {
+        val context = SpaEnvironmentFactory.instance.appContext
+        val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
+        return createBrowseIntent(context, browseActivityClass, entryId)
+    }
+
     fun createBrowseIntent(
         context: Context?,
         browseActivityClass: Class<out Activity>?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 151b50cd..60599d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -30,14 +30,14 @@
     val name: String
 
     /** The display name of this page provider, for better readability. */
-    val displayName: String?
-        get() = null
+    val displayName: String
+        get() = name
 
     /** The page parameters, default is no parameters. */
     val parameter: List<NamedNavArgument>
         get() = emptyList()
 
-    fun getTitle(arguments: Bundle?): String = displayName ?: name
+    fun getTitle(arguments: Bundle?): String = displayName
 
     fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index a9cb041..9bb06f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,10 +17,12 @@
 package com.android.settingslib.spa.framework.common
 
 import android.app.Activity
+import android.content.BroadcastReceiver
 import android.content.Context
 import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.slice.SettingsSliceDataRepository
 
 private const val TAG = "SpaEnvironment"
 
@@ -46,6 +48,10 @@
         Log.d(TAG, "resetForPreview")
     }
 
+    fun isReady(): Boolean {
+        return spaEnvironment != null
+    }
+
     val instance: SpaEnvironment
         get() {
             if (spaEnvironment == null)
@@ -59,13 +65,15 @@
 
     val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
 
+    val sliceDataRepository = SettingsSliceDataRepository()
+
     // In Robolectric test, applicationContext is not available. Use context as fallback.
     val appContext: Context = context.applicationContext ?: context
 
     open val browseActivityClass: Class<out Activity>? = null
-
+    open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
     open val searchProviderAuthorities: String? = null
-
+    open val sliceProviderAuthorities: String? = null
     open val logger: SpaLogger = object : SpaLogger {}
 
     // TODO: add other environment setup here.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 26491d5..760064a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.framework.debug
 
+import android.net.Uri
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
@@ -38,6 +39,8 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.slice.presenter.SliceDemo
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -47,6 +50,7 @@
 private const val ROUTE_ROOT = "root"
 private const val ROUTE_All_PAGES = "pages"
 private const val ROUTE_All_ENTRIES = "entries"
+private const val ROUTE_All_SLICES = "slices"
 private const val ROUTE_PAGE = "page"
 private const val ROUTE_ENTRY = "entry"
 private const val PARAM_NAME_PAGE_ID = "pid"
@@ -81,6 +85,7 @@
                 composable(route = ROUTE_ROOT) { RootPage() }
                 composable(route = ROUTE_All_PAGES) { AllPages() }
                 composable(route = ROUTE_All_ENTRIES) { AllEntries() }
+                composable(route = ROUTE_All_SLICES) { AllSlices() }
                 composable(
                     route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
                     arguments = listOf(
@@ -102,6 +107,8 @@
         val entryRepository by spaEnvironment.entryRepository
         val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
         val allEntry = remember { entryRepository.getAllEntries() }
+        val allSliceEntry =
+            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
         HomeScaffold(title = "Settings Debug") {
             Preference(object : PreferenceModel {
                 override val title = "List All Pages (${allPageWithEntry.size})"
@@ -111,6 +118,10 @@
                 override val title = "List All Entries (${allEntry.size})"
                 override val onClick = navigator(route = ROUTE_All_ENTRIES)
             })
+            Preference(object : PreferenceModel {
+                override val title = "List All Slices (${allSliceEntry.size})"
+                override val onClick = navigator(route = ROUTE_All_SLICES)
+            })
         }
     }
 
@@ -140,6 +151,19 @@
     }
 
     @Composable
+    fun AllSlices() {
+        val entryRepository by spaEnvironment.entryRepository
+        val authority = spaEnvironment.sliceProviderAuthorities
+        val allSliceEntry =
+            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
+        RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
+            for (entry in allSliceEntry) {
+                SliceDemo(sliceUri = entry.createSliceUri(authority))
+            }
+        }
+    }
+
+    @Composable
     fun OnePage(arguments: Bundle?) {
         val context = LocalContext.current
         val entryRepository by spaEnvironment.entryRepository
@@ -221,6 +245,18 @@
     }
 }
 
+private fun SettingsEntry.createSliceUri(
+    authority: String?,
+    runtimeArguments: Bundle? = null
+): Uri {
+    if (authority == null) return Uri.EMPTY
+    return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
+        route = this.containerPage().buildRoute(),
+        entryId = this.id,
+        runtimeArguments = runtimeArguments,
+    ).build()
+}
+
 /**
  * A blank activity without any page.
  */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
new file mode 100644
index 0000000..d8d2378
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.slice
+
+import android.net.Uri
+import android.util.Log
+import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+private const val TAG = "SliceDataRepository"
+
+class SettingsSliceDataRepository {
+    // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
+    private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
+
+    // Note: mark this function synchronized, so that we can get the same livedata during the
+    // whole lifecycle of a Slice.
+    @Synchronized
+    fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
+        val sliceString = sliceUri.getSliceId() ?: return null
+        return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
+            sliceDataMap[sliceString] = it
+            it
+        }
+    }
+
+    fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
+        val sliceString = sliceUri.getSliceId() ?: return null
+        val sliceData = sliceDataMap[sliceString] ?: return null
+        return if (sliceData.isActive()) sliceData else null
+    }
+
+    private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
+        Log.d(TAG, "buildLiveData: $sliceUri")
+        if (!SpaEnvironmentFactory.isReady()) return null
+
+        val entryRepository by SpaEnvironmentFactory.instance.entryRepository
+        val entryId = sliceUri.getEntryId() ?: return null
+        val entry = entryRepository.getEntry(entryId) ?: return null
+        if (!entry.hasSliceSupport) return null
+
+        val arguments = sliceUri.getRuntimeArguments()
+        return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
new file mode 100644
index 0000000..ff143ed
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.slice
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+// Defines SliceUri, which contains special query parameters:
+//  -- KEY_DESTINATION: The route that this slice is navigated to.
+//  -- KEY_HIGHLIGHT_ENTRY: The entry id of this slice
+//  Other parameters can considered as runtime parameters.
+// Use {entryId, runtimeParams} as the unique Id of this Slice.
+typealias SliceUri = Uri
+
+val RESERVED_KEYS = listOf(
+    KEY_DESTINATION,
+    KEY_HIGHLIGHT_ENTRY
+)
+
+fun SliceUri.getEntryId(): String? {
+    return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun SliceUri.getDestination(): String? {
+    return getQueryParameter(KEY_DESTINATION)
+}
+
+fun SliceUri.getRuntimeArguments(): Bundle {
+    val params = Bundle()
+    for (queryName in queryParameterNames) {
+        if (RESERVED_KEYS.contains(queryName)) continue
+        params.putString(queryName, getQueryParameter(queryName))
+    }
+    return params
+}
+
+fun SliceUri.getSliceId(): String? {
+    val entryId = getEntryId() ?: return null
+    val params = getRuntimeArguments()
+    return "${entryId}_$params"
+}
+
+fun Uri.Builder.appendSliceParams(
+    route: String? = null,
+    entryId: String? = null,
+    runtimeArguments: Bundle? = null
+): Uri.Builder {
+    if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+    if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
+    if (runtimeArguments != null) {
+        for (key in runtimeArguments.keySet()) {
+            appendQueryParameter(key, runtimeArguments.getString(key, ""))
+        }
+    }
+    return this
+}
+
+fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val sliceBroadcastClass =
+        SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
+    val entryId = getEntryId() ?: return null
+    return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
+}
+
+fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val destination = getDestination() ?: return null
+    val entryId = getEntryId()
+    return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+fun Intent.createBrowsePendingIntent(): PendingIntent? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+    val destination = getStringExtra(KEY_DESTINATION) ?: return null
+    val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+    return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
+}
+
+private fun createBrowsePendingIntent(
+    context: Context,
+    browseActivityClass: Class<out Activity>,
+    destination: String,
+    entryId: String?
+): PendingIntent {
+    val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+        .apply {
+            // Set both extra and data (which is a Uri) in Slice Intent:
+            // 1) extra is used in SPA navigation framework
+            // 2) data is used in Slice framework
+            putExtra(KEY_DESTINATION, destination)
+            if (entryId != null) {
+                putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+            }
+            data = Uri.Builder().appendSliceParams(destination, entryId).build()
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK
+        }
+
+    return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+}
+
+private fun createBroadcastPendingIntent(
+    context: Context,
+    sliceBroadcastClass: Class<out BroadcastReceiver>,
+    entryId: String
+): PendingIntent {
+    val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
+        .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+    return PendingIntent.getBroadcast(
+        context, 0 /* requestCode */, intent,
+        PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
new file mode 100644
index 0000000..cff1c0c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.slice.presenter
+
+import android.net.Uri
+import androidx.compose.material3.Divider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.widget.SliceLiveData
+import androidx.slice.widget.SliceView
+
+@Composable
+fun SliceDemo(sliceUri: Uri) {
+    val context = LocalContext.current
+    val lifecycleOwner = LocalLifecycleOwner.current
+    val sliceData = remember {
+        SliceLiveData.fromUri(context, sliceUri)
+    }
+
+    Divider()
+    AndroidView(
+        factory = { localContext ->
+            val view = SliceView(localContext)
+            view.setShowTitleItems(true)
+            view.isScrollable = false
+            view
+        },
+        update = { view -> sliceData.observe(lifecycleOwner, view) }
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
new file mode 100644
index 0000000..b65b91f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.slice.provider
+
+import android.app.PendingIntent
+import android.content.Context
+import android.net.Uri
+import androidx.core.graphics.drawable.IconCompat
+import androidx.slice.Slice
+import androidx.slice.SliceManager
+import androidx.slice.builders.ListBuilder
+import androidx.slice.builders.SliceAction
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.slice.createBroadcastPendingIntent
+import com.android.settingslib.spa.slice.createBrowsePendingIntent
+
+fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+    val intent = sliceUri.createBrowsePendingIntent() ?: return null
+    return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
+    val intent = sliceUri.createBroadcastPendingIntent() ?: return null
+    return createDemoSlice(sliceUri, title, summary, intent)
+}
+
+fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
+    val context = SpaEnvironmentFactory.instance.appContext
+    if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
+    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
+        .addRow(ListBuilder.RowBuilder().apply {
+            setPrimaryAction(createSliceAction(context, intent))
+            setTitle(title)
+            setSubtitle(summary)
+        }).build()
+}
+
+private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
+    return SliceAction.create(
+        intent,
+        IconCompat.createWithResource(
+            context,
+            com.google.android.material.R.drawable.navigation_empty_icon
+        ),
+        ListBuilder.ICON_IMAGE,
+        "Enter app"
+    )
+}
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 529a201..2d501fc 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -21,11 +21,12 @@
 
 android {
     namespace 'com.android.settingslib.spa.tests'
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
@@ -60,7 +61,6 @@
 dependencies {
     androidTestImplementation project(":spa")
     androidTestImplementation project(":testutils")
-    androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
     androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
     androidTestImplementation "com.google.truth:truth:1.1.3"
     androidTestImplementation "org.mockito:mockito-android:3.4.6"
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 31d2ae4..2017d53 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -63,7 +63,7 @@
         assertThat(entry.toPage).isNull()
         assertThat(entry.isAllowSearch).isFalse()
         assertThat(entry.isSearchDataDynamic).isFalse()
-        assertThat(entry.mutableStatus).isFalse()
+        assertThat(entry.hasMutableStatus).isFalse()
     }
 
     @Test
@@ -133,7 +133,7 @@
         assertThat(entry.toPage).isNull()
         assertThat(entry.isAllowSearch).isTrue()
         assertThat(entry.isSearchDataDynamic).isFalse()
-        assertThat(entry.mutableStatus).isTrue()
+        assertThat(entry.hasMutableStatus).isTrue()
     }
 
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 539e56b..7097a5d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -118,6 +118,7 @@
         page.enterPage()
         page.leavePage()
         page.enterPage()
+        assertThat(page.createBrowseIntent()).isNotNull()
         assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
             .isEqualTo(2)
         assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 71d7d8a..cbfbb9c 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -20,11 +20,12 @@
 }
 
 android {
-    compileSdk spa_target_sdk
+    compileSdk TARGET_SDK
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdk spa_min_sdk
-        targetSdk spa_target_sdk
+        minSdk MIN_SDK
+        targetSdk TARGET_SDK
     }
 
     sourceSets {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index cec6d7d..b3638c2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -28,7 +28,6 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
 import com.android.settingslib.spaprivileged.model.app.userHandle
-import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import org.junit.Before
 import org.junit.Rule
@@ -77,7 +76,7 @@
             }
         }
 
-        assertThat(storageSize.value).isEqualTo("123 B")
+        composeTestRule.waitUntil { storageSize.value == "123 B" }
     }
 
     companion object {
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
     }
 
     companion object {
-        private const val TAG = "ThemedBatteryDrawable"
-        private const val WIDTH = 12f
-        private const val HEIGHT = 20f
+        const val WIDTH = 12f
+        const val HEIGHT = 20f
         private const val CRITICAL_LEVEL = 15
         // On a 12x20 grid, how wide to make the fill protection stroke.
         // Scales when our size changes
         private const val PROTECTION_STROKE_WIDTH = 3f
         // Arbitrarily chosen for visibility at small sizes
-        private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+        const val PROTECTION_MIN_STROKE_WIDTH = 6f
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index aea2f52..f5c9bcd 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -884,7 +884,7 @@
                         Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING,
                         Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
                         Settings.Secure.SPATIAL_AUDIO_ENABLED,
-                        Settings.Secure.TIMEOUT_TO_USER_ZERO,
+                        Settings.Secure.TIMEOUT_TO_DOCK_USER,
                         Settings.Secure.UI_NIGHT_MODE_LAST_COMPUTED,
                         Settings.Secure.UI_NIGHT_MODE_OVERRIDE_OFF,
                         Settings.Secure.UI_NIGHT_MODE_OVERRIDE_ON);
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce9829b..55d6379 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -485,6 +485,12 @@
     <!-- Whether to show a severe low battery dialog. -->
     <bool name="config_severe_battery_dialog">false</bool>
 
+    <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+         needed. This path is a 10px wide and 13px tall. -->
+    <string name="config_batterymeterShieldPath" translatable="false">
+        M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+    </string>
+
     <!-- A path similar to frameworks/base/core/res/res/values/config.xml
       config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
       cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f9f2195..6577b07 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
     so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
     <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
 
+    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+         @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+         the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+         bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6824d7f..3270e45 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -316,7 +316,7 @@
     <!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
     <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_unlock_button">Unlock</string>
+    <string name="accessibility_unlock_button">Unlocked</string>
     <!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_lock_icon">Device locked</string>
     <!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -439,11 +439,17 @@
     <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
 
     <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
 
     <!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
 
+    <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+
+    <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+
     <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
     <string name="accessibility_overflow_action">See all notifications</string>
 
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index af4be1a..e07a6c1 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
         android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toStartOf="@id/begin_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
     <Constraint
         android:id="@+id/date">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             android:layout_marginStart="8dp"
             app:layout_constrainedWidth="true"
@@ -57,7 +57,7 @@
     <Constraint
         android:id="@+id/statusIcons">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toEndOf="@id/date"
@@ -65,6 +65,7 @@
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="packed"
             />
     </Constraint>
 
@@ -80,12 +81,16 @@
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="packed"
             />
     </Constraint>
 
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
+            app:layout_constraintWidth_min="48dp"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             />
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
index d8a4e77..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -43,6 +43,7 @@
             app:layout_constraintBottom_toBottomOf="@id/carrier_group"
             app:layout_constraintEnd_toStartOf="@id/carrier_group"
             app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
         <Transform
             android:scaleX="2.57"
@@ -53,7 +54,7 @@
     <Constraint
         android:id="@+id/date">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toStartOf="@id/space"
@@ -67,16 +68,15 @@
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-            android:minHeight="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintWidth_min="48dp"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toEndOf="@id/clock"
             app:layout_constraintTop_toBottomOf="@id/privacy_container"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_bias="1"
             app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
             />
         <PropertySet
             android:alpha="1"
@@ -86,7 +86,7 @@
     <Constraint
         android:id="@+id/statusIcons">
         <Layout
-            android:layout_width="0dp"
+            android:layout_width="wrap_content"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constrainedWidth="true"
             app:layout_constraintStart_toEndOf="@id/space"
@@ -108,6 +108,7 @@
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintHorizontal_bias="1"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
             />
     </Constraint>
 
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f..36ac1ff 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,6 +34,7 @@
 import platform.test.screenshot.DeviceEmulationRule
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.PathConfig
 import platform.test.screenshot.ScreenshotTestRule
 import platform.test.screenshot.getEmulatedDevicePathConfig
 import platform.test.screenshot.matchers.BitmapMatcher
@@ -41,13 +42,19 @@
 /** A rule for View screenshot diff unit tests. */
 class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+    pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
+    assetsPathRelativeToRepo: String = ""
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
     private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
     private val screenshotRule =
         ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+            if (assetsPathRelativeToRepo.isBlank()) {
+                SystemUIGoldenImagePathManager(pathConfig)
+            } else {
+                SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
+            }
         )
     private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
     private val delegateRule =
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 485a0d3..8a0fca0 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -63,9 +63,6 @@
     resource_dirs: [
         "res",
     ],
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
     kotlincflags: ["-Xjvm-default=enable"],
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
deleted file mode 100644
index 5eda045..0000000
--- a/packages/SystemUI/shared/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
--keepattributes Signature
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 32ce537..9e58500 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,14 +18,11 @@
 
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.plugins.log.LogLevel.ERROR
 import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.plugins.log.LogLevel.VERBOSE
 import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -37,18 +34,16 @@
  * an overkill.
  */
 class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
-    fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
 
-    fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+    fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
 
-    fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+    fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
 
-    fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+    fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
 
-    fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
-
-    private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
-        buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+    fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+        buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
     }
 
     fun v(msg: String, arg: Any) {
@@ -61,17 +56,24 @@
 
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarCalculatedAlpha(alpha: Float) {
-        debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+        buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
     }
 
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarExplicitAlpha(alpha: Float) {
-        debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+        buffer.log(
+            TAG,
+            DEBUG,
+            { double1 = alpha.toDouble() },
+            { "new mExplicitAlpha value: $double1" }
+        )
     }
 
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
-        debugLog(
+        buffer.log(
+            TAG,
+            DEBUG,
             {
                 int1 = visibility
                 double1 = alpha.toDouble()
@@ -80,4 +82,22 @@
             { "changing visibility to $int1 with alpha $double1 in state: $str1" }
         )
     }
+
+    @JvmOverloads
+    fun logBiometricMessage(
+        @CompileTimeConstant context: String,
+        msgId: Int? = null,
+        msg: String? = null
+    ) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = context
+                str2 = "$msgId"
+                str3 = msg
+            },
+            { "$str1 msgId: $str2 msg: $str3" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+    private val context: Context,
+    frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+    private val mainBatteryDrawable: ThemedBatteryDrawable
+        get() = drawable as ThemedBatteryDrawable
+
+    private val shieldPath = Path()
+    private val scaledShield = Path()
+    private val scaleMatrix = Matrix()
+
+    private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+    private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+    private var density = context.resources.displayMetrics.density
+
+    private val dualTone =
+        context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+    private val shieldTransparentOutlinePaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.color = Color.TRANSPARENT
+            p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+            p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+            p.style = Paint.Style.FILL_AND_STROKE
+        }
+
+    private val shieldPaint =
+        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+            p.color = Color.MAGENTA
+            p.style = Paint.Style.FILL
+            p.isDither = true
+        }
+
+    init {
+        loadPaths()
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+        updateSizes()
+    }
+
+    var displayShield: Boolean = false
+
+    private fun updateSizes() {
+        val b = bounds
+        if (b.isEmpty) {
+            return
+        }
+
+        val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+        val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+        drawable?.setBounds(
+            b.left,
+            b.top,
+            /* right= */ b.left + mainWidth.toInt(),
+            /* bottom= */ b.top + mainHeight.toInt()
+        )
+
+        if (displayShield) {
+            val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+            val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+            scaleMatrix.setScale(sx, sy)
+            shieldPath.transform(scaleMatrix, scaledShield)
+
+            shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+            shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+            val scaledStrokeWidth =
+                (sx * SHIELD_STROKE).coerceAtLeast(
+                    ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+                )
+            shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+        }
+    }
+
+    override fun getIntrinsicHeight(): Int {
+        val height =
+            if (displayShield) {
+                BATTERY_HEIGHT_WITH_SHIELD
+            } else {
+                BATTERY_HEIGHT
+            }
+        return (height * density).toInt()
+    }
+
+    override fun getIntrinsicWidth(): Int {
+        val width =
+            if (displayShield) {
+                BATTERY_WIDTH_WITH_SHIELD
+            } else {
+                BATTERY_WIDTH
+            }
+        return (width * density).toInt()
+    }
+
+    override fun draw(c: Canvas) {
+        c.saveLayer(null, null)
+        // Draw the main battery icon
+        super.draw(c)
+
+        if (displayShield) {
+            c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+            // We need a transparent outline around the shield, so first draw the transparent-ness
+            // then draw the shield
+            c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+            c.drawPath(scaledShield, shieldPaint)
+        }
+        c.restore()
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.OPAQUE
+    }
+
+    override fun setAlpha(p0: Int) {
+        // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+    }
+
+    override fun setColorFilter(colorfilter: ColorFilter?) {
+        super.setColorFilter(colorFilter)
+        shieldPaint.colorFilter = colorFilter
+    }
+
+    /** Sets whether the battery is currently charging. */
+    fun setCharging(charging: Boolean) {
+        mainBatteryDrawable.charging = charging
+    }
+
+    /** Sets the current level (out of 100) of the battery. */
+    fun setBatteryLevel(level: Int) {
+        mainBatteryDrawable.setBatteryLevel(level)
+    }
+
+    /** Sets whether power save is enabled. */
+    fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+        mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+    }
+
+    /** Returns whether power save is currently enabled. */
+    fun getPowerSaveEnabled(): Boolean {
+        return mainBatteryDrawable.powerSaveEnabled
+    }
+
+    /** Sets the colors to use for the icon. */
+    fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+        shieldPaint.color = if (dualTone) fgColor else singleToneColor
+        mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+    }
+
+    /** Notifies this drawable that the density might have changed. */
+    fun notifyDensityChanged() {
+        density = context.resources.displayMetrics.density
+    }
+
+    private fun loadPaths() {
+        val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+        shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settingslib.graph.ThemedBatteryDrawable;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
     public static final int MODE_OFF = 2;
     public static final int MODE_ESTIMATE = 3;
 
-    private final ThemedBatteryDrawable mDrawable;
+    private final AccessorizedBatteryDrawable mDrawable;
     private final ImageView mBatteryIconView;
     private TextView mBatteryPercentView;
 
@@ -77,7 +76,10 @@
     private int mLevel;
     private int mShowPercentMode = MODE_DEFAULT;
     private boolean mShowPercentAvailable;
+    private String mEstimateText = null;
     private boolean mCharging;
+    private boolean mIsOverheated;
+    private boolean mDisplayShieldEnabled;
     // Error state where we know nothing about the current battery state
     private boolean mBatteryStateUnknown;
     // Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
         final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
                 context.getColor(R.color.meter_background_color));
         mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
-        mDrawable = new ThemedBatteryDrawable(context, frameColor);
+        mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
         atts.recycle();
 
         mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
         if (mode == mShowPercentMode) return;
         mShowPercentMode = mode;
         updateShowPercent();
+        updatePercentText();
     }
 
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         updatePercentView();
+        mDrawable.notifyDensityChanged();
     }
 
     public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
         mDrawable.setPowerSaveEnabled(isPowerSave);
     }
 
+    void onIsOverheatedChanged(boolean isOverheated) {
+        boolean valueChanged = mIsOverheated != isOverheated;
+        mIsOverheated = isOverheated;
+        if (valueChanged) {
+            updateContentDescription();
+            // The battery drawable is a different size depending on whether it's currently
+            // overheated or not, so we need to re-scale the view when overheated changes.
+            scaleBatteryMeterViews();
+        }
+    }
+
     private TextView loadPercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
         mBatteryEstimateFetcher = fetcher;
     }
 
+    void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+        mDisplayShieldEnabled = displayShieldEnabled;
+    }
+
     void updatePercentText() {
         if (mBatteryStateUnknown) {
-            setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
             return;
         }
 
         if (mBatteryEstimateFetcher == null) {
+            setPercentTextAtCurrentLevel();
             return;
         }
 
@@ -245,10 +264,9 @@
                         return;
                     }
                     if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+                        mEstimateText = estimate;
                         mBatteryPercentView.setText(estimate);
-                        setContentDescription(getContext().getString(
-                                R.string.accessibility_battery_level_with_estimate,
-                                mLevel, estimate));
+                        updateContentDescription();
                     } else {
                         setPercentTextAtCurrentLevel();
                     }
@@ -257,28 +275,49 @@
                 setPercentTextAtCurrentLevel();
             }
         } else {
-            setContentDescription(
-                    getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
-                            : R.string.accessibility_battery_level, mLevel));
+            updateContentDescription();
         }
     }
 
     private void setPercentTextAtCurrentLevel() {
-        if (mBatteryPercentView == null) {
-            return;
+        if (mBatteryPercentView != null) {
+            mEstimateText = null;
+            String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+            // Setting text actually triggers a layout pass (because the text view is set to
+            // wrap_content width and TextView always relayouts for this). Avoid needless
+            // relayout if the text didn't actually change.
+            if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+                mBatteryPercentView.setText(percentText);
+            }
         }
 
-        String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
-        // Setting text actually triggers a layout pass (because the text view is set to
-        // wrap_content width and TextView always relayouts for this). Avoid needless
-        // relayout if the text didn't actually change.
-        if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
-            mBatteryPercentView.setText(percentText);
+        updateContentDescription();
+    }
+
+    private void updateContentDescription() {
+        Context context = getContext();
+
+        String contentDescription;
+        if (mBatteryStateUnknown) {
+            contentDescription = context.getString(R.string.accessibility_battery_unknown);
+        } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+            contentDescription = context.getString(
+                    mIsOverheated
+                            ? R.string.accessibility_battery_level_charging_paused_with_estimate
+                            : R.string.accessibility_battery_level_with_estimate,
+                    mLevel,
+                    mEstimateText);
+        } else if (mIsOverheated) {
+            contentDescription =
+                    context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+        } else if (mCharging) {
+            contentDescription =
+                    context.getString(R.string.accessibility_battery_level_charging, mLevel);
+        } else {
+            contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
         }
 
-        setContentDescription(
-                getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
-                        : R.string.accessibility_battery_level, mLevel));
+        setContentDescription(contentDescription);
     }
 
     void updateShowPercent() {
@@ -329,6 +368,7 @@
         }
 
         mBatteryStateUnknown = isUnknown;
+        updateContentDescription();
 
         if (mBatteryStateUnknown) {
             mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
         res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
         float iconScaleFactor = typedValue.getFloat();
 
-        int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
-        int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+        float mainBatteryHeight =
+                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+        float mainBatteryWidth =
+                res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+        // If the battery is marked as overheated, we should display a shield indicating that the
+        // battery is being "defended".
+        boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+        float fullBatteryIconHeight =
+                BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+        float fullBatteryIconWidth =
+                BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+        int marginTop;
+        if (displayShield) {
+            // If the shield is displayed, we need some extra marginTop so that the bottom of the
+            // main icon is still aligned with the bottom of all the other system icons.
+            int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+            // However, the other system icons have some embedded bottom padding that the battery
+            // doesn't have, so we shouldn't move the battery icon down by the full amount.
+            // See b/258672854.
+            marginTop = shieldHeightAddition
+                    - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+        } else {
+            marginTop = 0;
+        }
+
         int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
 
         LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
-                (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
-        scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+                Math.round(fullBatteryIconWidth),
+                Math.round(fullBatteryIconHeight));
+        scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
 
+        mDrawable.setDisplayShield(displayShield);
         mBatteryIconView.setLayoutParams(scaledLayoutParams);
+        mBatteryIconView.invalidateDrawable(mDrawable);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..77cb9d1 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -29,6 +29,8 @@
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -84,6 +86,11 @@
                 public void onBatteryUnknownStateChanged(boolean isUnknown) {
                     mView.onBatteryUnknownStateChanged(isUnknown);
                 }
+
+                @Override
+                public void onIsOverheatedChanged(boolean isOverheated) {
+                    mView.onIsOverheatedChanged(isOverheated);
+                }
             };
 
     // Some places may need to show the battery conditionally, and not obey the tuner
@@ -98,6 +105,7 @@
             BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
+            FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
         mConfigurationController = configurationController;
@@ -106,6 +114,7 @@
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+        mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
         mSettingObserver = new SettingObserver(mainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+    /** Width of the main battery icon, not including the shield. */
+    const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+    /** Height of the main battery icon, not including the shield. */
+    const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+    private const val SHIELD_WIDTH = 10f
+    private const val SHIELD_HEIGHT = 13f
+
+    /**
+     * Amount that the left side of the shield should be offset from the left side of the battery.
+     */
+    const val SHIELD_LEFT_OFFSET = 8f
+    /** Amount that the top of the shield should be offset from the top of the battery. */
+    const val SHIELD_TOP_OFFSET = 10f
+
+    const val SHIELD_STROKE = 4f
+
+    /** The full width of the battery icon, including the main battery icon *and* the shield. */
+    const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+    /** The full height of the battery icon, including the main battery icon *and* the shield. */
+    const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+    /**
+     * Given the desired height of the main battery icon in pixels, returns the height that the full
+     * battery icon will take up in pixels.
+     *
+     * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+     * extends slightly below the bottom of the main battery icon so we need some extra height.
+     */
+    @JvmStatic
+    fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            mainBatteryHeight
+        } else {
+            val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+            verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+        }
+    }
+
+    /**
+     * Given the desired width of the main battery icon in pixels, returns the width that the full
+     * battery icon will take up in pixels.
+     *
+     * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+     * past the right side of the main battery icon so we need some extra width.
+     */
+    @JvmStatic
+    fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            mainBatteryWidth
+        } else {
+            val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+            horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+        }
+    }
+
+    /**
+     * Given the height of the full battery icon, return how tall the main battery icon should be.
+     *
+     * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+     * up some of the view's height so the main battery width will be just a portion of
+     * [fullBatteryHeight].
+     */
+    @JvmStatic
+    fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            fullBatteryHeight
+        } else {
+            return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+        }
+    }
+
+    /**
+     * Given the width of the full battery icon, return how wide the main battery icon should be.
+     *
+     * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+     * up some of the view's width so the main battery width will be just a portion of
+     * [fullBatteryWidth].
+     */
+    @JvmStatic
+    fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+        return if (!displayShield) {
+            fullBatteryWidth
+        } else {
+            return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
new file mode 100644
index 0000000..794eba4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+  "presubmit": [
+    {
+      // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+      "name": "SystemUIGoogleBiometricsScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index c619648..7dde947 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -13,7 +13,6 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -24,14 +23,17 @@
         view: CredentialPasswordView,
         host: CredentialView.Host,
         viewModel: CredentialViewModel,
+        requestFocusForInput: Boolean,
     ) {
         val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
 
         val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
 
         view.repeatWhenAttached {
-            passwordField.requestFocus()
-            passwordField.scheduleShowSoftInput()
+            if (requestFocusForInput) {
+                passwordField.requestFocus()
+                passwordField.scheduleShowSoftInput()
+            }
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 // observe credential validation attempts and submit/cancel buttons
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index 4765551..b692ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,7 +9,6 @@
 import com.android.systemui.biometrics.ui.CredentialView
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPatternView]. */
@@ -30,7 +29,7 @@
                     viewModel.header.collect { header ->
                         lockPatternView.setOnPatternListener(
                             OnPatternDetectedListener { pattern ->
-                                if (pattern.isPatternLongEnough()) {
+                                if (pattern.isPatternTooShort()) {
                                     // Pattern size is less than the minimum
                                     // do not count it as a failed attempt
                                     viewModel.showPatternTooShortError()
@@ -71,5 +70,5 @@
     }
 }
 
-private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
     size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index fcc9487..e2d36dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,7 +17,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
@@ -40,6 +39,7 @@
         panelViewController: AuthPanelController,
         animatePanel: Boolean,
         maxErrorDuration: Long = 3_000L,
+        requestFocusForInput: Boolean = true,
     ) {
         val titleView: TextView = view.requireViewById(R.id.title)
         val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,7 +110,8 @@
 
         // bind the auth widget
         when (view) {
-            is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+            is CredentialPasswordView ->
+                CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
             is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
             else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index fe89c9a..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,24 +21,21 @@
 import com.android.systemui.dagger.qualifiers.InstrumentationTest;
 import com.android.systemui.util.InitializationChecker;
 
-import javax.inject.Singleton;
-
 import dagger.BindsInstance;
-import dagger.Component;
 
 /**
  * Base root component for Dagger injection.
  *
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
  * See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
  */
-@Singleton
-@Component(modules = {GlobalModule.class})
 public interface GlobalRootComponent {
 
     /**
      * Builder for a GlobalRootComponent.
      */
-    @Component.Builder
     interface Builder {
         @BindsInstance
         Builder context(Context context);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index cedd850a..c01cf43 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -33,6 +33,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.controls.management.ControlsListingController;
 import com.android.systemui.controls.ui.ControlsActivity;
@@ -42,6 +43,8 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.util.ViewController;
 
+import java.util.List;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -76,16 +79,25 @@
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final ControlsComponent mControlsComponent;
 
-        private boolean mControlServicesAvailable = false;
+        private boolean mOverlayActive = false;
 
         // Callback for when the home controls service availability changes.
         private final ControlsListingController.ControlsListingCallback mControlsCallback =
-                serviceInfos -> {
-                    boolean available = !serviceInfos.isEmpty();
+                services -> updateHomeControlsComplication();
 
-                    if (available != mControlServicesAvailable) {
-                        mControlServicesAvailable = available;
-                        updateComplicationAvailability();
+        private final DreamOverlayStateController.Callback mOverlayStateCallback =
+                new DreamOverlayStateController.Callback() {
+                    @Override
+                    public void onStateChanged() {
+                        if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
+                            return;
+                        }
+
+                        mOverlayActive = !mOverlayActive;
+
+                        if (mOverlayActive) {
+                            updateHomeControlsComplication();
+                        }
                     }
                 };
 
@@ -102,18 +114,29 @@
         public void start() {
             mControlsComponent.getControlsListingController().ifPresent(
                     c -> c.addCallback(mControlsCallback));
+            mDreamOverlayStateController.addCallback(mOverlayStateCallback);
         }
 
-        private void updateComplicationAvailability() {
+        private void updateHomeControlsComplication() {
+            mControlsComponent.getControlsListingController().ifPresent(c -> {
+                if (isHomeControlsAvailable(c.getCurrentServices())) {
+                    mDreamOverlayStateController.addComplication(mComplication);
+                } else {
+                    mDreamOverlayStateController.removeComplication(mComplication);
+                }
+            });
+        }
+
+        private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) {
+            if (controlsServices.isEmpty()) {
+                return false;
+            }
+
             final boolean hasFavorites = mControlsComponent.getControlsController()
                     .map(c -> !c.getFavorites().isEmpty())
                     .orElse(false);
-            if (!hasFavorites || !mControlServicesAvailable
-                    || mControlsComponent.getVisibility() == UNAVAILABLE) {
-                mDreamOverlayStateController.removeComplication(mComplication);
-            } else {
-                mDreamOverlayStateController.addComplication(mComplication);
-            }
+            final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
+            return hasFavorites && visibility != UNAVAILABLE;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index f9dca08..101f4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -44,7 +44,7 @@
             DreamOverlayComponent.class,
         })
 public interface DreamModule {
-    String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+    String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
 
     String DREAM_SUPPORTED = "dream_supported";
 
@@ -70,10 +70,10 @@
 
     /** */
     @Provides
-    @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
-    static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+    @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+    static boolean providesDreamOnlyEnabledForDockUser(@Main Resources resources) {
         return resources.getBoolean(
-                com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+                com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c4cc338..076e705 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -82,7 +82,10 @@
     // TODO(b/257506350): Tracking Bug
     val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
 
-    // next id: 118
+    // TODO(b/257315550): Tracking Bug
+    val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+    // next id: 119
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -239,6 +242,9 @@
     // TODO(b/256613548): Tracking Bug
     val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
 
+    // TODO(b/256623670): Tracking Bug
+    @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
     val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
@@ -380,7 +386,8 @@
 
     // TODO(b/254513155): Tracking Bug
     @JvmField
-    val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy")
+    val SCREENSHOT_WORK_PROFILE_POLICY =
+        unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0697133..f92bbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -364,13 +364,18 @@
     private void distributeTiles() {
         emptyAndInflateOrRemovePages();
 
-        final int tileCount = mPages.get(0).maxTiles();
-        if (DEBUG) Log.d(TAG, "Distributing tiles");
+        final int tilesPerPageCount = mPages.get(0).maxTiles();
         int index = 0;
-        final int NT = mTiles.size();
-        for (int i = 0; i < NT; i++) {
+        final int totalTilesCount = mTiles.size();
+        if (DEBUG) {
+            Log.d(TAG, "Distributing tiles: "
+                    + "[tilesPerPageCount=" + tilesPerPageCount + "]"
+                    + "[totalTilesCount=" + totalTilesCount + "]"
+            );
+        }
+        for (int i = 0; i < totalTilesCount; i++) {
             TileRecord tile = mTiles.get(i);
-            if (mPages.get(index).mRecords.size() == tileCount) index++;
+            if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
             if (DEBUG) {
                 Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
                         + index);
@@ -577,8 +582,8 @@
         });
         setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
         int dx = getWidth() * lastPageNumber;
-        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx  : dx, 0,
-            REVEAL_SCROLL_DURATION_MILLIS);
+        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
+                REVEAL_SCROLL_DURATION_MILLIS);
         postInvalidateOnAnimation();
     }
 
@@ -738,6 +743,7 @@
 
     public interface PageListener {
         int INVALID_PAGE = -1;
+
         void onPageChanged(boolean isFirst, int pageNumber);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3d00dd4..7ee4047 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -123,7 +123,6 @@
     public boolean updateResources() {
         final Resources res = mContext.getResources();
         mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
-        updateColumns();
         mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
         mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
         mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cf10c79..79fcc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -82,12 +82,12 @@
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
+
+        updateTransparentViewHeight();
     }
 
     void updateResources() {
-        LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
-        lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
-        mTransparentView.setLayoutParams(lp);
+        updateTransparentViewHeight();
         mRecyclerView.getAdapter().notifyItemChanged(0);
     }
 
@@ -236,4 +236,10 @@
     public boolean isOpening() {
         return mOpening;
     }
+
+    private void updateTransparentViewHeight() {
+        LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
+        lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+        mTransparentView.setLayoutParams(lp);
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index bebd580..4abe309 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -70,7 +70,7 @@
     private final SettingObserver mDreamSettingObserver;
     private final UserTracker mUserTracker;
     private final boolean mDreamSupported;
-    private final boolean mDreamOnlyEnabledForSystemUser;
+    private final boolean mDreamOnlyEnabledForDockUser;
 
     private boolean mIsDocked = false;
 
@@ -100,8 +100,8 @@
             BroadcastDispatcher broadcastDispatcher,
             UserTracker userTracker,
             @Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
-            @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
-                    boolean dreamOnlyEnabledForSystemUser
+            @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+                    boolean dreamOnlyEnabledForDockUser
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -123,7 +123,7 @@
         };
         mUserTracker = userTracker;
         mDreamSupported = dreamSupported;
-        mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+        mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
     }
 
     @Override
@@ -203,7 +203,8 @@
         // For now, restrict to debug users.
         return Build.isDebuggable()
                 && mDreamSupported
-                && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+                // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
+                && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..954534d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -92,7 +92,6 @@
                     centerEnd,
                     ConstraintSet.END
                 )
-                constrainWidth(R.id.statusIcons, 0)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 15f4b12..2101efb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -55,7 +55,6 @@
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -64,7 +63,6 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.text.format.Formatter;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
@@ -76,6 +74,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
@@ -123,7 +122,6 @@
 
     private static final String TAG = "KeyguardIndication";
     private static final boolean DEBUG_CHARGING_SPEED = false;
-    private static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     private static final int MSG_HIDE_TRANSIENT = 1;
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
@@ -139,6 +137,7 @@
     protected final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AuthController mAuthController;
+    private final KeyguardLogger mKeyguardLogger;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -229,7 +228,8 @@
             ScreenLifecycle screenLifecycle,
             KeyguardBypassController keyguardBypassController,
             AccessibilityManager accessibilityManager,
-            FaceHelpMessageDeferral faceHelpMessageDeferral) {
+            FaceHelpMessageDeferral faceHelpMessageDeferral,
+            KeyguardLogger keyguardLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mDevicePolicyManager = devicePolicyManager;
@@ -249,6 +249,7 @@
         mKeyguardBypassController = keyguardBypassController;
         mAccessibilityManager = accessibilityManager;
         mScreenLifecycle = screenLifecycle;
+        mKeyguardLogger = keyguardLogger;
         mScreenLifecycle.addObserver(mScreenObserver);
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
@@ -1024,7 +1025,7 @@
                 mChargingTimeRemaining = mPowerPluggedIn
                         ? mBatteryInfo.computeChargeTimeRemaining() : -1;
             } catch (RemoteException e) {
-                Log.e(TAG, "Error calling IBatteryStats: ", e);
+                mKeyguardLogger.logException(e, "Error calling IBatteryStats");
                 mChargingTimeRemaining = -1;
             }
             updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
@@ -1072,8 +1073,10 @@
             final boolean isCoExFaceAcquisitionMessage =
                     faceAuthSoftError && isUnlockWithFingerprintPossible;
             if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
-                debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
-                        + ", due to co-ex logic");
+                mKeyguardLogger.logBiometricMessage(
+                        "skipped showing help message due to co-ex logic",
+                        msgId,
+                        helpString);
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
                         mInitialTextColorState);
@@ -1131,7 +1134,7 @@
             CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
             mFaceAcquiredMessageDeferral.reset();
             if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
-                debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+                mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -1145,8 +1148,9 @@
 
         private void onFingerprintAuthError(int msgId, String errString) {
             if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
-                debugLog("suppressingFingerprintError msgId=" + msgId
-                        + " errString= " + errString);
+                mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+                        msgId,
+                        errString);
             } else {
                 showErrorMessageNowOrLater(errString, null);
             }
@@ -1282,7 +1286,8 @@
     }
 
     private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
-        debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+        mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
+                null, String.valueOf(deferredFaceMessage));
         if (canUnlockWithFingerprint()) {
             // Co-ex: show deferred message OR nothing
             // if we're on the lock screen (bouncer isn't showing), show the deferred msg
@@ -1294,7 +1299,8 @@
                 );
             } else {
                 // otherwise, don't show any message
-                debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+                mKeyguardLogger.logBiometricMessage(
+                        "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
             }
         } else if (deferredFaceMessage != null) {
             // Face-only: The face timeout message is not very actionable, let's ask the
@@ -1315,12 +1321,6 @@
                 KeyguardUpdateMonitor.getCurrentUser());
     }
 
-    private void debugLog(String logMsg) {
-        if (DEBUG) {
-            Log.d(TAG, logMsg);
-        }
-    }
-
     private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
             mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0369845..2d580ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -225,6 +225,7 @@
             BroadcastDispatcher broadcastDispatcher,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
+            FeatureFlags featureFlags,
             BatteryController batteryController
     ) {
         return new BatteryMeterViewController(
@@ -234,6 +235,7 @@
                 broadcastDispatcher,
                 mainHandler,
                 contentResolver,
+                featureFlags,
                 batteryController);
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
 
         default void onWirelessChargingChanged(boolean isWirlessCharging) {
         }
+
+        default void onIsOverheatedChanged(boolean isOverheated) {
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..3c2ac7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
 import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
     protected boolean mPowerSave;
     private boolean mAodPowerSave;
     private boolean mWirelessCharging;
+    private boolean mIsOverheated = false;
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
@@ -184,6 +188,7 @@
         cb.onPowerSaveChanged(mPowerSave);
         cb.onBatteryUnknownStateChanged(mStateUnknown);
         cb.onWirelessChargingChanged(mWirelessCharging);
+        cb.onIsOverheatedChanged(mIsOverheated);
     }
 
     @Override
@@ -222,6 +227,13 @@
                 fireBatteryUnknownStateChanged();
             }
 
+            int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+            boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+            if (isOverheated != mIsOverheated) {
+                mIsOverheated = isOverheated;
+                fireIsOverheatedChanged();
+            }
+
             fireBatteryLevelChanged();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
             updatePowerSave();
@@ -292,6 +304,10 @@
         return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
 
+    public boolean isOverheated() {
+        return mIsOverheated;
+    }
+
     @Override
     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +418,15 @@
         }
     }
 
+    private void fireIsOverheatedChanged() {
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+            }
+        }
+    }
+
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +437,7 @@
         String plugged = args.getString("plugged");
         String powerSave = args.getString("powersave");
         String present = args.getString("present");
+        String overheated = args.getString("overheated");
         if (level != null) {
             mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
         }
@@ -426,6 +452,10 @@
             mStateUnknown = !present.equals("true");
             fireBatteryUnknownStateChanged();
         }
+        if (overheated != null) {
+            mIsOverheated = overheated.equals("true");
+            fireIsOverheatedChanged();
+        }
         fireBatteryLevelChanged();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+    @Test
+    fun intrinsicSize_shieldFalse_isBatterySize() {
+        val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+        drawable.displayShield = false
+
+        val density = context.resources.displayMetrics.density
+        assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+        assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+    }
+
+    @Test
+    fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+        val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+        drawable.displayShield = true
+
+        val density = context.resources.displayMetrics.density
+        assertThat(drawable.intrinsicHeight)
+            .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+        assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+    }
+
+    // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..bc8f961 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -35,6 +35,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -59,6 +61,7 @@
     private Handler mHandler;
     @Mock
     private ContentResolver mContentResolver;
+    private FakeFeatureFlags mFeatureFlags;
     @Mock
     private BatteryController mBatteryController;
 
@@ -71,19 +74,13 @@
         when(mBatteryMeterView.getContext()).thenReturn(mContext);
         when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
 
-        mController = new BatteryMeterViewController(
-                mBatteryMeterView,
-                mConfigurationController,
-                mTunerService,
-                mBroadcastDispatcher,
-                mHandler,
-                mContentResolver,
-                mBatteryController
-        );
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
     }
 
     @Test
     public void onViewAttached_callbacksRegistered() {
+        initController();
         mController.onViewAttached();
 
         verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
 
     @Test
     public void onViewDetached_callbacksUnregistered() {
+        initController();
         // Set everything up first.
         mController.onViewAttached();
 
@@ -114,6 +112,7 @@
 
     @Test
     public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+        initController();
         // Start out receiving tuner updates
         mController.onViewAttached();
 
@@ -124,10 +123,43 @@
 
     @Test
     public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+        initController();
+
         mController.ignoreTunerUpdates();
 
         mController.onViewAttached();
 
         verify(mTunerService, never()).addTunable(any(), any());
     }
+
+    @Test
+    public void shieldFlagDisabled_viewNotified() {
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+        initController();
+
+        verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+    }
+
+    @Test
+    public void shieldFlagEnabled_viewNotified() {
+        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+        initController();
+
+        verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+    }
+
+    private void initController() {
+        mController = new BatteryMeterViewController(
+                mBatteryMeterView,
+                mConfigurationController,
+                mTunerService,
+                mBroadcastDispatcher,
+                mHandler,
+                mContentResolver,
+                mFeatureFlags,
+                mBatteryController
+        );
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
 import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
         // No assert needed
     }
 
+    @Test
+    fun contentDescription_unknown() {
+        mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_unknown)
+        )
+    }
+
+    @Test
+    fun contentDescription_estimate() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+                )
+        )
+    }
+
+    @Test
+    fun contentDescription_estimateAndOverheated() {
+        mBatteryMeterView.onBatteryLevelChanged(17, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_charging_paused_with_estimate,
+                        17,
+                        ESTIMATE,
+                )
+        )
+    }
+
+    @Test
+    fun contentDescription_overheated() {
+        mBatteryMeterView.onBatteryLevelChanged(90, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+        )
+    }
+
+    @Test
+    fun contentDescription_charging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging, 45)
+        )
+    }
+
+    @Test
+    fun contentDescription_notCharging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 45)
+        )
+    }
+
+    @Test
+    fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+        mBatteryMeterView.onBatteryLevelChanged(15, false)
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+        mBatteryMeterView.updatePercentText()
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+                )
+        )
+
+        // Update the show mode from estimate to percent
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+        assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 15)
+        )
+    }
+
+    @Test
+    fun contentDescription_manyUpdates_alwaysUpdated() {
+        // Overheated
+        mBatteryMeterView.onBatteryLevelChanged(90, false)
+        mBatteryMeterView.onIsOverheatedChanged(true)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+        )
+
+        // Overheated & estimate
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+        mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+        mBatteryMeterView.updatePercentText()
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_charging_paused_with_estimate,
+                        90,
+                        ESTIMATE,
+                )
+        )
+
+        // Just estimate
+        mBatteryMeterView.onIsOverheatedChanged(false)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(
+                        R.string.accessibility_battery_level_with_estimate,
+                        90,
+                        ESTIMATE,
+                )
+        )
+
+        // Just percent
+        mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 90)
+        )
+
+        // Charging
+        mBatteryMeterView.onBatteryLevelChanged(90, true)
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level_charging, 90)
+        )
+    }
+
+    @Test
+    fun isOverheatedChanged_true_drawableGetsTrue() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(drawable.displayShield).isTrue()
+    }
+
+    @Test
+    fun isOverheatedChanged_false_drawableGetsFalse() {
+        mBatteryMeterView.setDisplayShieldEnabled(true)
+        val drawable = getBatteryDrawable()
+
+        // Start as true
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        // Update to false
+        mBatteryMeterView.onIsOverheatedChanged(false)
+
+        assertThat(drawable.displayShield).isFalse()
+    }
+
+    @Test
+    fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+        mBatteryMeterView.setDisplayShieldEnabled(false)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsOverheatedChanged(true)
+
+        assertThat(drawable.displayShield).isFalse()
+    }
+
+    private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+        return (mBatteryMeterView.getChildAt(0) as ImageView)
+                .drawable as AccessorizedBatteryDrawable
+    }
+
     private class Fetcher : BatteryEstimateFetcher {
         override fun fetchBatteryTimeRemainingEstimate(
                 completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
     private companion object {
         const val ESTIMATE = "2 hours 2 minutes"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+    @Test
+    fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+        val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+        assertThat(fullHeight).isEqualTo(56f)
+    }
+
+    @Test
+    fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+        val mainHeight = BATTERY_HEIGHT * 5
+        val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+        // Since the main battery was scaled 5x, the output height should also be scaled 5x
+        val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+        assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+    }
+
+    @Test
+    fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+        val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+        assertThat(fullWidth).isEqualTo(33f)
+    }
+
+    @Test
+    fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+        val mainWidth = BATTERY_WIDTH * 3.3f
+
+        val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+        // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+        val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+        assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+    }
+
+    @Test
+    fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+        val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+        assertThat(mainHeight).isEqualTo(89f)
+    }
+
+    @Test
+    fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+        val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+        val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+        // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+        val expectedHeight = BATTERY_HEIGHT * 7.7f
+        assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+    }
+
+    @Test
+    fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+        val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+        assertThat(mainWidth).isEqualTo(2345f)
+    }
+
+    @Test
+    fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+        val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+        val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+        // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+        val expectedWidth = BATTERY_WIDTH * 0.6f
+        assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index aa8c93e..30ad485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -90,7 +90,10 @@
     private ActivityStarter mActivityStarter;
 
     @Mock
-    UiEventLogger mUiEventLogger;
+    private UiEventLogger mUiEventLogger;
+
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
 
     @Before
     public void setup() {
@@ -164,6 +167,29 @@
         verify(mDreamOverlayStateController).addComplication(mComplication);
     }
 
+    @Test
+    public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
+        final DreamHomeControlsComplication.Registrant registrant =
+                new DreamHomeControlsComplication.Registrant(mComplication,
+                        mDreamOverlayStateController, mControlsComponent);
+        registrant.start();
+
+        setServiceAvailable(true);
+        setHaveFavorites(false);
+
+        // Complication not available on start.
+        verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+
+        // Favorite controls added, complication should be available now.
+        setHaveFavorites(true);
+
+        // Dream overlay becomes active.
+        setDreamOverlayActive(true);
+
+        // Verify complication is added.
+        verify(mDreamOverlayStateController).addComplication(mComplication);
+    }
+
     /**
      * Ensures clicking home controls chip logs UiEvent.
      */
@@ -196,10 +222,17 @@
 
     private void setServiceAvailable(boolean value) {
         final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+        when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
         when(serviceInfos.isEmpty()).thenReturn(!value);
         triggerControlsListingCallback(serviceInfos);
     }
 
+    private void setDreamOverlayActive(boolean value) {
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
+        verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
+        mStateCallbackCaptor.getValue().onStateChanged();
+    }
+
     private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
         verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
         mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 5abc0e1..35c8cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -27,6 +27,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -42,16 +44,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TileLayoutTest extends SysuiTestCase {
-    private TileLayout mTileLayout;
+    private Resources mResources;
     private int mLayoutSizeForOneTile;
+    private TileLayout mTileLayout; // under test
 
     @Before
     public void setUp() throws Exception {
-        mTileLayout = new TileLayout(mContext);
+        Context context = Mockito.spy(mContext);
+        mResources = Mockito.spy(context.getResources());
+        Mockito.when(mContext.getResources()).thenReturn(mResources);
+
+        mTileLayout = new TileLayout(context);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
@@ -203,4 +211,21 @@
         verify(tileRecord1.tileView).setPosition(0);
         verify(tileRecord2.tileView).setPosition(1);
     }
+
+    @Test
+    public void resourcesChanged_updateResources_returnsTrue() {
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        mTileLayout.updateResources(); // setup with 1
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+
+        assertEquals(true, mTileLayout.updateResources());
+    }
+
+    @Test
+    public void resourcesSame_updateResources_returnsFalse() {
+        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        mTileLayout.updateResources(); // setup with 1
+
+        assertEquals(false, mTileLayout.updateResources());
+    }
 }
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 0ce9056..d7eb337 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -320,6 +321,48 @@
         assertThat(changes.largeScreenConstraintsChanges).isNull()
     }
 
+    @Test
+    fun testRelevantViewsAreNotMatchConstraints() {
+        val views = mapOf(
+                R.id.clock to "clock",
+                R.id.date to "date",
+                R.id.statusIcons to "icons",
+                R.id.privacy_container to "privacy",
+                R.id.carrier_group to "carriers",
+                R.id.batteryRemainingIcon to "battery",
+        )
+        views.forEach { (id, name) ->
+            assertWithMessage("$name has 0 height in qqs")
+                    .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+            assertWithMessage("$name has 0 width in qqs")
+                    .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+            assertWithMessage("$name has 0 height in qs")
+                    .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+            assertWithMessage("$name has 0 width in qs")
+                    .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+        }
+    }
+
+    @Test
+    fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() {
+        val views = mapOf(
+                R.id.clock to "clock",
+                R.id.date to "date",
+                R.id.statusIcons to "icons",
+                R.id.privacy_container to "privacy",
+                R.id.carrier_group to "carriers",
+                R.id.batteryRemainingIcon to "battery",
+        )
+        views.forEach { (id, name) ->
+            assertWithMessage("$name changes height")
+                    .that(qqsConstraint.getConstraint(id).layout.mHeight)
+                    .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight)
+            assertWithMessage("$name changes width")
+                    .that(qqsConstraint.getConstraint(id).layout.mWidth)
+                    .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth)
+        }
+    }
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index fe4da47..e8a7ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -87,6 +87,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -271,7 +272,7 @@
                 mUserManager, mExecutor, mExecutor, mFalsingManager,
                 mAuthController, mLockPatternUtils, mScreenLifecycle,
                 mKeyguardBypassController, mAccessibilityManager,
-                mFaceHelpMessageDeferral);
+                mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
 
         Assert.assertFalse(mBatteryController.isChargingSourceDock());
     }
+
+    @Test
+    public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isOverheated());
+    }
+
+    @Test
+    public void batteryStateChanged_healthOverheated_outputsTrue() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertTrue(mBatteryController.isOverheated());
+    }
+
+    @Test
+    public void batteryStateChanged_noHealthGiven_outputsFalse() {
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isOverheated());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d42f757..db3b9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1331,7 +1331,7 @@
         spyOn(mContext);
         mBubbleController.updateBubble(mBubbleEntry);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
                 Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
@@ -1351,7 +1351,7 @@
         mBubbleController.updateBubble(mBubbleEntry);
         mBubbleData.setExpanded(true);
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
 
@@ -1365,7 +1365,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
         Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         i.putExtra("reason", "gestureNav");
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
@@ -1379,7 +1379,7 @@
         mBubbleData.setExpanded(true);
 
         verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
-                mFilterArgumentCaptor.capture());
+                mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
 
         Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
         mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
diff --git a/packages/VpnDialogs/Android.bp b/packages/VpnDialogs/Android.bp
index 05135b2..e4f80e2 100644
--- a/packages/VpnDialogs/Android.bp
+++ b/packages/VpnDialogs/Android.bp
@@ -23,10 +23,15 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+android_library {
+    name: "VpnDialogsLib",
+    srcs: ["src/**/*.java"],
+}
+
 android_app {
     name: "VpnDialogs",
     certificate: "platform",
     privileged: true,
-    srcs: ["src/**/*.java"],
+    static_libs: ["VpnDialogsLib"],
     platform_apis: true,
 }
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index f971a09..28e7272 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -100,4 +100,33 @@
          without any consequences. [CHAR LIMIT=20] -->
     <string name="dismiss">Dismiss</string>
 
+    <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+         into displaying what they want. The system will attempt to sanitize the label, and if the
+         label is deemed dangerous, then this string is used instead. The first argument is the
+         first 30 characters of the label, and the second argument is the package name of the app.
+         Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+         "My VPN app wants to set up a VPN connection...". If the label is very long, then, this
+         will be used to show "VerylongVPNlabel… (com.my.vpn.app) wants to set up a VPN
+         connection...". For this case, the code will refer to sanitized_vpn_label_with_ellipsis.
+    -->
+    <string name="sanitized_vpn_label_with_ellipsis">
+        <xliff:g id="sanitized_vpn_label_with_ellipsis" example="My VPN app">%1$s</xliff:g>… (
+        <xliff:g id="sanitized_vpn_label_with_ellipsis" example="com.my.vpn.app">%2$s</xliff:g>)
+    </string>
+
+    <!-- Malicious VPN apps may provide very long labels or cunning HTML to trick the system dialogs
+         into displaying what they want. The system will attempt to sanitize the label, and if the
+         label is deemed dangerous, then this string is used instead. The first argument is the
+         label, and the second argument is the package name of the app.
+         Example : Normally a VPN app may be called "My VPN app" in which case the dialog will read
+         "My VPN app wants to set up a VPN connection...". If the VPN label contains HTML tag but
+         the length is not very long, the dialog will show "VpnLabelWith&lt;br&gt;HtmlTag
+         (com.my.vpn.app) wants to set up a VPN connection...". For this case, the code will refer
+         to sanitized_vpn_label.
+    -->
+    <string name="sanitized_vpn_label">
+        <xliff:g id="sanitized_vpn_label" example="My VPN app">%1$s</xliff:g> (
+        <xliff:g id="sanitized_vpn_label" example="com.my.vpn.app">%2$s</xliff:g>)
+    </string>
+
 </resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index fb23678..a98d6d8 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -33,6 +33,7 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.net.VpnConfig;
 
@@ -40,12 +41,19 @@
         implements DialogInterface.OnClickListener, ImageGetter {
     private static final String TAG = "VpnConfirm";
 
+    // Usually the label represents the app name, 150 code points might be enough to display the app
+    // name, and 150 code points won't cover the warning message from VpnDialog.
+    @VisibleForTesting
+    static final int MAX_VPN_LABEL_LENGTH = 150;
+
     @VpnManager.VpnType private final int mVpnType;
 
     private String mPackage;
 
     private VpnManager mVm;
 
+    private View mView;
+
     public ConfirmDialog() {
         this(VpnManager.TYPE_VPN_SERVICE);
     }
@@ -54,6 +62,43 @@
         mVpnType = vpnType;
     }
 
+    /**
+     * This function will use the string resource to combine the VPN label and the package name.
+     *
+     * If the VPN label violates the length restriction, the first 30 code points of VPN label and
+     * the package name will be returned. Or return the VPN label and the package name directly if
+     * the VPN label doesn't violate the length restriction.
+     *
+     * The result will be something like,
+     * - ThisIsAVeryLongVpnAppNameWhich... (com.vpn.app)
+     *   if the VPN label violates the length restriction.
+     * or
+     * - VpnLabelWith&lt;br&gt;HtmlTag (com.vpn.app)
+     *   if the VPN label doesn't violate the length restriction.
+     *
+     */
+    private String getSimplifiedLabel(String vpnLabel, String packageName) {
+        if (vpnLabel.codePointCount(0, vpnLabel.length()) > 30) {
+            return getString(R.string.sanitized_vpn_label_with_ellipsis,
+                    vpnLabel.substring(0, vpnLabel.offsetByCodePoints(0, 30)),
+                            packageName);
+        }
+
+        return getString(R.string.sanitized_vpn_label, vpnLabel, packageName);
+    }
+
+    @VisibleForTesting
+    protected String getSanitizedVpnLabel(String vpnLabel, String packageName) {
+        final String sanitizedVpnLabel = Html.escapeHtml(vpnLabel);
+        final boolean exceedMaxVpnLabelLength = sanitizedVpnLabel.codePointCount(0,
+                sanitizedVpnLabel.length()) > MAX_VPN_LABEL_LENGTH;
+        if (exceedMaxVpnLabelLength || !vpnLabel.equals(sanitizedVpnLabel)) {
+            return getSimplifiedLabel(sanitizedVpnLabel, packageName);
+        }
+
+        return sanitizedVpnLabel;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -75,15 +120,16 @@
             finish();
             return;
         }
-        View view = View.inflate(this, R.layout.confirm, null);
-        ((TextView) view.findViewById(R.id.warning)).setText(
-                Html.fromHtml(getString(R.string.warning, getVpnLabel()),
-                        this, null /* tagHandler */));
+        mView = View.inflate(this, R.layout.confirm, null);
+        ((TextView) mView.findViewById(R.id.warning)).setText(
+                Html.fromHtml(getString(R.string.warning, getSanitizedVpnLabel(
+                        getVpnLabel().toString(), mPackage)),
+                        this /* imageGetter */, null /* tagHandler */));
         mAlertParams.mTitle = getText(R.string.prompt);
         mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
         mAlertParams.mPositiveButtonListener = this;
         mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
-        mAlertParams.mView = view;
+        mAlertParams.mView = mView;
         setupAlert();
 
         getWindow().setCloseOnTouchOutside(false);
@@ -92,6 +138,11 @@
         button.setFilterTouchesWhenObscured(true);
     }
 
+    @VisibleForTesting
+    public CharSequence getWarningText() {
+        return ((TextView) mView.findViewById(R.id.warning)).getText();
+    }
+
     private CharSequence getVpnLabel() {
         try {
             return VpnConfig.getVpnLabel(this, mPackage);
diff --git a/packages/VpnDialogs/tests/Android.bp b/packages/VpnDialogs/tests/Android.bp
new file mode 100644
index 0000000..68639bd
--- /dev/null
+++ b/packages/VpnDialogs/tests/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "VpnDialogsTests",
+    // Use platform certificate because the test will invoke a hidden API.
+    // (e.g. VpnManager#prepareVpn()).
+    certificate: "platform",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
+        "VpnDialogsLib",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/packages/VpnDialogs/tests/AndroidManifest.xml b/packages/VpnDialogs/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f26c1fe
--- /dev/null
+++ b/packages/VpnDialogs/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.vpndialogs.tests">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.vpndialogs.VpnDialogTest$InstrumentedConfirmDialog"/>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.vpndialogs.tests"
+                     android:label="Vpn dialog tests">
+    </instrumentation>
+</manifest>
diff --git a/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
new file mode 100644
index 0000000..7cfa466
--- /dev/null
+++ b/packages/VpnDialogs/tests/src/com/android/vpndialogs/VpnDialogTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.vpndialogs;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.VpnManager;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class VpnDialogTest {
+    private ActivityScenario<ConfirmDialog> mActivityScenario;
+
+    @SuppressWarnings("StaticMockMember")
+    @Mock
+    private static PackageManager sPm;
+
+    @SuppressWarnings("StaticMockMember")
+    @Mock
+    private static VpnManager sVm;
+
+    @Mock
+    private ApplicationInfo mAi;
+
+    private static final String VPN_APP_NAME = "VpnApp";
+    private static final String VPN_APP_PACKAGE_NAME = "com.android.vpndialogs.VpnDialogTest";
+    private static final String VPN_LABEL_CONTAINS_HTML_TAG =
+            "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a>";
+    private static final String VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION =
+            "<b><a href=\"https://www.malicious.vpn.app.com\">Google Play</a></b>"
+            + " Wants to connect the network. <br></br><br></br><br></br><br></br><br></br>"
+            + " <br></br><br></br><br></br><br></br><br></br><br></br><br></br><br></br> Deny it?";
+    private static final String VPN_LABEL_VIOLATES_LENGTH_RESTRICTION = "This is a VPN label"
+            + " which violates the length restriction. The length restriction here are 150 code"
+            + " points. So the VPN label should be sanitized, and shows the package name to the"
+            + " user.";
+
+    public static class InstrumentedConfirmDialog extends ConfirmDialog {
+        @Override
+        public PackageManager getPackageManager() {
+            return sPm;
+        }
+
+        @Override
+        public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
+            switch (name) {
+                case Context.VPN_MANAGEMENT_SERVICE:
+                    return sVm;
+                default:
+                    return super.getSystemService(name);
+            }
+        }
+
+        @Override
+        public String getCallingPackage() {
+            return VPN_APP_PACKAGE_NAME;
+        }
+    }
+
+    private void launchActivity() {
+        final Context context = getInstrumentation().getContext();
+        mActivityScenario = ActivityScenario.launch(
+                new Intent(context, InstrumentedConfirmDialog.class));
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withNormalCase() throws Exception {
+        // Test the normal case that the VPN label showed in the VpnDialog is the app name.
+        doReturn(VPN_APP_NAME).when(mAi).loadLabel(sPm);
+        launchActivity();
+        mActivityScenario.onActivity(activity -> {
+            assertTrue(activity.getWarningText().toString().contains(VPN_APP_NAME));
+        });
+    }
+
+    private void verifySanitizedVpnLabel(String originalLabel) {
+        doReturn(originalLabel).when(mAi).loadLabel(sPm);
+        launchActivity();
+        mActivityScenario.onActivity(activity -> {
+            // The VPN label was sanitized because violating length restriction or having a html
+            // tag, so the warning message will contain the package name.
+            assertTrue(activity.getWarningText().toString().contains(activity.getCallingPackage()));
+            // Also, the length of sanitized VPN label shouldn't longer than MAX_VPN_LABEL_LENGTH
+            // and it shouldn't contain html tag.
+            final String sanitizedVpnLabel =
+                    activity.getSanitizedVpnLabel(originalLabel, VPN_APP_PACKAGE_NAME);
+            assertTrue(sanitizedVpnLabel.codePointCount(0, sanitizedVpnLabel.length())
+                    < ConfirmDialog.MAX_VPN_LABEL_LENGTH);
+            assertFalse(sanitizedVpnLabel.contains("<b>"));
+        });
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withHtmlTag() throws Exception {
+        // Test the case that the VPN label was sanitized because there is a html tag.
+        verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG);
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withHtmlTagAndViolateLengthRestriction() throws Exception {
+        // Test the case that the VPN label was sanitized because there is a html tag.
+        verifySanitizedVpnLabel(VPN_LABEL_CONTAINS_HTML_TAG_AND_VIOLATE_LENGTH_RESTRICTION);
+    }
+
+    @Test
+    public void testGetSanitizedVpnLabel_withLengthRestriction() throws Exception {
+        // Test the case that the VPN label was sanitized because hitting the length restriction.
+        verifySanitizedVpnLabel(VPN_LABEL_VIOLATES_LENGTH_RESTRICTION);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(false).when(sVm).prepareVpn(anyString(), anyString(), anyInt());
+        doReturn(null).when(sPm).queryIntentServices(any(), anyInt());
+        doReturn(mAi).when(sPm).getApplicationInfo(anyString(), anyInt());
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 000bafe..ce7854d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -86,6 +86,15 @@
     }
 
     /**
+     * For communicating when activities are blocked from entering PIP on the display by this
+     * policy controller.
+     */
+    public interface PipBlockedCallback {
+        /** Called when an activity is blocked from entering PIP. */
+        void onEnteringPipBlocked(int uid);
+    }
+
+    /**
      * If required, allow the secure activity to display on remote device since
      * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
      */
@@ -112,6 +121,7 @@
     @GuardedBy("mGenericWindowPolicyControllerLock")
     final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
+    @Nullable private final PipBlockedCallback mPipBlockedCallback;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
@@ -155,6 +165,7 @@
             @NonNull Set<ComponentName> blockedActivities,
             @ActivityPolicy int defaultActivityPolicy,
             @NonNull ActivityListener activityListener,
+            @NonNull PipBlockedCallback pipBlockedCallback,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
             @NonNull SecureWindowCallback secureWindowCallback,
             @AssociationRequest.DeviceProfile String deviceProfile) {
@@ -169,6 +180,7 @@
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
         mDeviceProfile = deviceProfile;
+        mPipBlockedCallback = pipBlockedCallback;
         mSecureWindowCallback = secureWindowCallback;
     }
 
@@ -317,6 +329,17 @@
         }
     }
 
+    @Override
+    public boolean isEnteringPipAllowed(int uid) {
+        if (super.isEnteringPipAllowed(uid)) {
+            return true;
+        }
+        mHandler.post(() -> {
+            mPipBlockedCallback.onEnteringPipBlocked(uid);
+        });
+        return false;
+    }
+
     /**
      * Returns true if an app with the given UID has an activity running on the virtual display for
      * this controller.
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 5ebbf07..be21075 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -624,6 +624,7 @@
                             mParams.getBlockedActivities(),
                             mParams.getDefaultActivityPolicy(),
                             createListenerAdapter(),
+                            this::onEnteringPipBlocked,
                             this::onActivityBlocked,
                             this::onSecureWindowShown,
                             mAssociationInfo.getDeviceProfile());
@@ -779,6 +780,11 @@
         return mVirtualDisplayIds.contains(displayId);
     }
 
+    void onEnteringPipBlocked(int uid) {
+        showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
+                Toast.LENGTH_LONG, mContext.getMainLooper());
+    }
+
     interface OnDeviceCloseListener {
         void onClose(int associationId);
     }
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 8055afc..2662e03 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -685,7 +685,9 @@
                         FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
                                 packageInfo.packageName,
                                 packageInfo.getLongVersionCode(),
-                                mBinaryHashes.get(packageInfo.packageName));
+                                mBinaryHashes.get(packageInfo.packageName),
+                                4,  // indicating that the digest is SHA256
+                                null);  // TODO: This is to comform to the extended schema.
                     }
                 }
             } catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 3c281d1..5114bd5 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -26,6 +26,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
@@ -171,6 +172,7 @@
                     return MODE_ALLOWED;
                 }
             case OP_RECORD_AUDIO:
+            case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO:
                 if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) == 0) {
                     return MODE_IGNORED;
                 } else {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index c06101f..bf0b388 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2511,19 +2511,22 @@
         float appliedThermalCapNits =
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
                 ? -1f : convertToNits(event.getThermalMax());
-
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                convertToNits(event.getInitialBrightness()),
-                convertToNits(event.getBrightness()),
-                event.getSlowAmbientLux(),
-                event.getPhysicalDisplayId(),
-                event.isShortTermModelActive(),
-                appliedLowPowerMode,
-                appliedRbcStrength,
-                appliedHbmMaxNits,
-                appliedThermalCapNits,
-                event.isAutomaticBrightnessEnabled(),
-                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                    convertToNits(event.getInitialBrightness()),
+                    convertToNits(event.getBrightness()),
+                    event.getSlowAmbientLux(),
+                    event.getPhysicalDisplayId(),
+                    event.isShortTermModelActive(),
+                    appliedLowPowerMode,
+                    appliedRbcStrength,
+                    appliedHbmMaxNits,
+                    appliedThermalCapNits,
+                    event.isAutomaticBrightnessEnabled(),
+                    FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        }
     }
 
     private final class DisplayControllerHandler extends Handler {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index facc6b2..4a0ba22 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -116,7 +116,7 @@
     private final DreamUiEventLogger mDreamUiEventLogger;
     private final ComponentName mAmbientDisplayComponent;
     private final boolean mDismissDreamOnActivityStart;
-    private final boolean mDreamsOnlyEnabledForSystemUser;
+    private final boolean mDreamsOnlyEnabledForDockUser;
     private final boolean mDreamsEnabledByDefaultConfig;
     private final boolean mDreamsActivatedOnChargeByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
@@ -214,8 +214,8 @@
                 mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
         AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
         mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
-        mDreamsOnlyEnabledForSystemUser =
-                mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
+        mDreamsOnlyEnabledForDockUser =
+                mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
         mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
                 R.bool.config_dismissDreamOnActivityStart);
 
@@ -292,10 +292,9 @@
             pw.println();
             pw.println("mCurrentDream=" + mCurrentDream);
             pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
-            pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+            pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser);
             pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
             pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
-            pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
             pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
             pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
             pw.println("mIsDocked=" + mIsDocked);
@@ -602,7 +601,8 @@
     }
 
     private boolean dreamsEnabledForUser(int userId) {
-        return !mDreamsOnlyEnabledForSystemUser || (userId == UserHandle.USER_SYSTEM);
+        // TODO(b/257333623): Support non-system Dock Users in HSUM.
+        return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
     }
 
     private ServiceInfo getServiceInfo(ComponentName name) {
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 6234421..298098a 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -17,10 +17,15 @@
 package com.android.server.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.view.InputChannel;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 
 import java.util.List;
 
@@ -136,6 +141,26 @@
     public abstract InputChannel createInputChannel(String inputChannelName);
 
     /**
+     * Pilfer pointers from the input channel with the given token so that ongoing gestures are
+     * canceled for all other channels.
+     */
+    public abstract void pilferPointers(IBinder token);
+
+    /**
+     * Called when the current input method and/or {@link InputMethodSubtype} is updated.
+     *
+     * @param userId User ID to be notified about.
+     * @param subtypeHandle A {@link InputMethodSubtypeHandle} corresponds to {@code subtype}.
+     * @param subtype A {@link InputMethodSubtype} object, or {@code null} when the current
+     *                {@link InputMethodSubtype} is not suitable for the physical keyboard layout
+     *                mapping.
+     * @see InputMethodSubtype#isSuitableForPhysicalKeyboardLayoutMapping()
+     */
+    public abstract void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+            @Nullable InputMethodSubtypeHandle subtypeHandle,
+            @Nullable InputMethodSubtype subtype);
+
+    /**
      * Increments keyboard backlight level if the device has an associated keyboard backlight
      * {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
      */
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c9a7646..cb615a9 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -23,6 +23,7 @@
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -111,11 +112,13 @@
 import android.view.SurfaceControl;
 import android.view.VerifiedInputEvent;
 import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
@@ -3791,6 +3794,21 @@
         }
 
         @Override
+        public void pilferPointers(IBinder token) {
+            mNative.pilferPointers(token);
+        }
+
+        @Override
+        public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
+                @Nullable InputMethodSubtypeHandle subtypeHandle,
+                @Nullable InputMethodSubtype subtype) {
+            if (DEBUG) {
+                Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+                        + " subtypeHandle=" + subtypeHandle);
+            }
+        }
+
+        @Override
         public void incrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
         }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 1a0f6f7..015e576 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -28,6 +28,7 @@
 import android.view.InputChannel;
 import android.view.MotionEvent;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
@@ -198,9 +199,10 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver) {
         try {
-            mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+            mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
             logRemoteException(e);
             return false;
@@ -210,9 +212,10 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+    boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver) {
         try {
-            mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+            mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
             logRemoteException(e);
             return false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4d1c5ae..8b083bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -129,6 +129,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
@@ -151,6 +152,7 @@
 import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
 import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
 import com.android.internal.inputmethod.IInputContentUriToken;
+import com.android.internal.inputmethod.IInputMethod;
 import com.android.internal.inputmethod.IInputMethodClient;
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.IInputMethodSession;
@@ -162,6 +164,7 @@
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -641,6 +644,10 @@
      */
     private boolean mInputShown;
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mCurStatsToken;
+
     /**
      * {@code true} if the current input method is in fullscreen mode.
      */
@@ -760,7 +767,7 @@
      * <dd>
      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
      * </dd>
-     * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
      * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
      *    currently invisible.
      * </dd>
@@ -784,8 +791,7 @@
 
     /**
      * Internal state snapshot when
-     * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IRemoteInputConnection, EditorInfo,
-     * boolean)} is about to be called.
+     * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
      *
      * <p>Calling that IPC endpoint basically means that
      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1070,7 +1076,7 @@
 
         /**
          * Add a new entry and discard the oldest entry as needed.
-         * @param info {@lin StartInputInfo} to be added.
+         * @param info {@link StartInputInfo} to be added.
          */
         void addEntry(@NonNull StartInputInfo info) {
             final int index = mNextIndex;
@@ -1188,18 +1194,18 @@
                 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
                     final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
                             mContext.getContentResolver(),
-                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
+                            Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0 /* def */, mUserId);
                     mAccessibilityRequestingNoSoftKeyboard =
                             (accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK)
                                     == AccessibilityService.SHOW_MODE_HIDDEN;
                     if (mAccessibilityRequestingNoSoftKeyboard) {
                         final boolean showRequested = mShowRequested;
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
                         mShowRequested = showRequested;
                     } else if (mShowRequested) {
-                        showCurrentInputLocked(mCurFocusedWindow,
-                                InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(mCurFocusedWindow,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
                 } else {
@@ -1665,8 +1671,8 @@
         }
         // Hide soft input before user switch task since switch task may block main handler a while
         // and delayed the hideCurrentInputLocked().
-        hideCurrentInputLocked(
-                mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
+        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER);
         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
                 clientToBeReset);
         mUserSwitchHandlerTask = task;
@@ -2221,7 +2227,7 @@
             }
             final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
             try {
-                client.asBinder().linkToDeath(deathRecipient, 0);
+                client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
             } catch (RemoteException e) {
                 throw new IllegalStateException(e);
             }
@@ -2246,7 +2252,7 @@
         synchronized (ImfLock.class) {
             ClientState cs = mClients.remove(client.asBinder());
             if (cs != null) {
-                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0);
+                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
                 clearClientSessionLocked(cs);
                 clearClientSessionForAccessibilityLocked(cs);
 
@@ -2259,8 +2265,8 @@
                 }
 
                 if (mCurClient == cs) {
-                    hideCurrentInputLocked(
-                            mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
                         IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -2307,6 +2313,8 @@
             mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
             mCurVirtualDisplayToScreenMatrix = null;
+            ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            mCurStatsToken = null;
 
             mMenuController.hideInputMethodMenuLocked();
         }
@@ -2379,8 +2387,11 @@
                 navButtonFlags, mCurImeDispatcher);
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
-            showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
-                    SoftInputShowHideReason.ATTACH_NEW_INPUT);
+            // Re-use current statsToken, if it exists.
+            final ImeTracker.Token statsToken = mCurStatsToken;
+            mCurStatsToken = null;
+            showCurrentInputLocked(mCurFocusedWindow, statsToken, getAppShowFlagsLocked(),
+                    null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
         String curId = getCurIdLocked();
@@ -2499,7 +2510,8 @@
 
         if (mDisplayIdToShowIme == INVALID_DISPLAY) {
             mImeHiddenByDisplayPolicy = true;
-            hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                    null /* resultReceiver */,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
             return InputBindResult.NO_IME;
         }
@@ -3201,6 +3213,18 @@
     }
 
     @GuardedBy("ImfLock.class")
+    private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId,
+            @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+        final InputMethodSubtype normalizedSubtype =
+                subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping()
+                        ? subtype : null;
+        final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
+                ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+        mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+                userId, newSubtypeHandle, normalizedSubtype);
+    }
+
+    @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
@@ -3209,8 +3233,10 @@
 
         // See if we need to notify a subtype change within the same IME.
         if (id.equals(getSelectedMethodIdLocked())) {
+            final int userId = mSettings.getCurrentUserId();
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
+                notifyInputMethodSubtypeChangedLocked(userId, info, null);
                 return;
             }
             final InputMethodSubtype oldSubtype = mCurrentSubtype;
@@ -3225,6 +3251,7 @@
             if (newSubtype == null || oldSubtype == null) {
                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
                         + ", new subtype = " + newSubtype);
+                notifyInputMethodSubtypeChangedLocked(userId, info, null);
                 return;
             }
             if (newSubtype != oldSubtype) {
@@ -3262,22 +3289,23 @@
     }
 
     @Override
-    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
-            int lastClickTooType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#showSoftInput");
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "showSoftInput")) {
+            if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
-                return showCurrentInputLocked(
-                        windowToken, flags, lastClickTooType, resultReceiver, reason);
+                return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType,
+                        resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3294,7 +3322,8 @@
                     "InputMethodManagerService#startStylusHandwriting");
             int uid = Binder.getCallingUid();
             synchronized (ImfLock.class) {
-                if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) {
+                if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
+                        null /* statsToken */)) {
                     return;
                 }
                 if (!hasSupportedStylusLocked()) {
@@ -3349,19 +3378,33 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean showCurrentInputLocked(IBinder windowToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        return showCurrentInputLocked(
-                windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+    boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        return showCurrentInputLocked(windowToken, statsToken, flags,
+                MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+    private boolean showCurrentInputLocked(IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        // Create statsToken is none exists.
+        if (statsToken == null) {
+            String packageName = null;
+            if (mCurEditorInfo != null) {
+                packageName = mCurEditorInfo.packageName;
+            }
+            statsToken = new ImeTracker.Token(packageName);
+            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
+                    reason);
+        }
+
         mShowRequested = true;
         if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
 
         if ((flags & InputMethodManager.SHOW_FORCED) != 0) {
             mShowExplicitlyRequested = true;
@@ -3371,8 +3414,10 @@
         }
 
         if (!mSystemReady) {
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
 
         mBindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3380,6 +3425,9 @@
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
+            ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+            mCurStatsToken = null;
             final int showFlags = getImeShowFlagsLocked();
             if (DEBUG) {
                 Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
@@ -3391,23 +3439,34 @@
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
-            if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
+            if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
                 onShowHideSoftInputRequested(true /* show */, windowToken, reason);
             }
             mInputShown = true;
             return true;
+        } else {
+            ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+            mCurStatsToken = statsToken;
         }
         return false;
     }
 
     @Override
-    public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+    public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+            @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput");
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "hideSoftInput")) {
+            if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
+                if (mInputShown) {
+                    ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                } else {
+                    ImeTracker.get().onCancelled(statsToken,
+                            ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+                }
                 return false;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -3415,7 +3474,7 @@
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
                 return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
-                        flags, resultReceiver, reason);
+                        statsToken, flags, resultReceiver, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3424,17 +3483,32 @@
     }
 
     @GuardedBy("ImfLock.class")
-    boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+    boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
+            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        // Create statsToken is none exists.
+        if (statsToken == null) {
+            String packageName = null;
+            if (mCurEditorInfo != null) {
+                packageName = mCurEditorInfo.packageName;
+            }
+            statsToken = new ImeTracker.Token(packageName);
+            ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+        }
+
         if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+
         if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+            ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
             return false;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
 
         // There is a chance that IMM#hideSoftInput() is called in a transient state where
         // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
@@ -3445,8 +3519,8 @@
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
         IInputMethodInvoker curMethod = getCurMethodLocked();
-        final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
-                || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+        final boolean shouldHideSoftInput = (curMethod != null)
+                && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
         boolean res;
         if (shouldHideSoftInput) {
             final Binder hideInputToken = new Binder();
@@ -3455,17 +3529,20 @@
             // 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.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             if (DEBUG) {
                 Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
                         + ", " + resultReceiver + ") for reason: "
                         + InputMethodDebug.softInputDisplayReasonToString(reason));
             }
             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
-            if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
+            if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
+                    resultReceiver)) {
                 onShowHideSoftInputRequested(false /* show */, windowToken, reason);
             }
             res = true;
         } else {
+            ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
             res = false;
         }
         mBindingController.setCurrentMethodNotVisible();
@@ -3473,6 +3550,9 @@
         mShowRequested = false;
         mShowExplicitlyRequested = false;
         mShowForced = false;
+        // Cancel existing statsToken for show IME as we got a hide request.
+        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+        mCurStatsToken = null;
         return res;
     }
 
@@ -3630,8 +3710,8 @@
             Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
                     + " a background user, use EditorInfo.targetInputMethodUser with"
                     + " INTERACT_ACROSS_USERS_FULL permission.");
-            hideCurrentInputLocked(
-                    mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER);
+            hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                    null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
             return InputBindResult.INVALID_USER;
         }
 
@@ -3687,7 +3767,7 @@
         boolean didStart = false;
 
         InputBindResult res = null;
-        // We shows the IME when the system allows the IME focused target window to restore the
+        // We show the IME when the system allows the IME focused target window to restore the
         // IME visibility (e.g. switching to the app task when last time the IME is visible).
         // Note that we don't restore IME visibility for some cases (e.g. when the soft input
         // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
@@ -3699,7 +3779,7 @@
             res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
                     editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
                     imeDispatcher);
-            showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+            showCurrentInputImplicitLocked(windowToken,
                     SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
             return res;
         }
@@ -3712,8 +3792,8 @@
                         // be behind any soft input window, so hide the
                         // soft input window if it is shown.
                         if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
-                        hideCurrentInputLocked(
-                                mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
 
                         // If focused display changed, we should unbind current method
@@ -3742,10 +3822,7 @@
                                 imeDispatcher);
                         didStart = true;
                     }
-                    showCurrentInputLocked(
-                            windowToken,
-                            InputMethodManager.SHOW_IMPLICIT,
-                            null,
+                    showCurrentInputImplicitLocked(windowToken,
                             SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
                 }
                 break;
@@ -3758,14 +3835,16 @@
             case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
                 if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                     if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
                 if (!sameWindowFocused) {
                     if (DEBUG) Slog.v(TAG, "Window asks to hide input");
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
                 }
                 break;
@@ -3781,7 +3860,7 @@
                                     imeDispatcher);
                             didStart = true;
                         }
-                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(windowToken,
                                 SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
                     } else {
                         Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
@@ -3802,7 +3881,7 @@
                                     imeDispatcher);
                             didStart = true;
                         }
-                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                        showCurrentInputImplicitLocked(windowToken,
                                 SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                     }
                 } else {
@@ -3824,7 +3903,8 @@
                     // an editor upon refocusing a window.
                     if (startInputByWinGainedFocus) {
                         if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
                     }
                 }
@@ -3838,7 +3918,8 @@
                     // 2) SOFT_INPUT_STATE_VISIBLE state without an editor
                     // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
                     if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */,
                             SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
                 }
                 res = startInputUncheckedLocked(cs, inputContext,
@@ -3853,8 +3934,15 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean canInteractWithImeLocked(
-            int uid, IInputMethodClient client, String methodName) {
+    private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken,
+            @SoftInputShowHideReason int reason) {
+        showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT,
+                null /* resultReceiver */, reason);
+    }
+
+    @GuardedBy("ImfLock.class")
+    private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
+            @Nullable ImeTracker.Token statsToken) {
         if (mCurClient == null || client == null
                 || mCurClient.mClient.asBinder() != client.asBinder()) {
             // We need to check if this is the current client with
@@ -3862,13 +3950,16 @@
             // be made before input is started in it.
             final ClientState cs = mClients.get(client.asBinder());
             if (cs == null) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
+            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.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
         return true;
     }
 
@@ -4221,7 +4312,7 @@
             final int curTokenDisplayId;
             synchronized (ImfLock.class) {
                 if (!canInteractWithImeLocked(callingUid, client,
-                        "getInputMethodWindowVisibleHeight")) {
+                        "getInputMethodWindowVisibleHeight", null /* statsToken */)) {
                     if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
                         EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
                         mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
@@ -4444,7 +4535,8 @@
 
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
+            if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
+                    null /* statsToken */)) {
                 return;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -4471,7 +4563,8 @@
 
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
-            if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
+            if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
+                    null /* statsToken */)) {
                 return;
             }
             final long ident = Binder.clearCallingIdentity();
@@ -4656,7 +4749,8 @@
     }
 
     @BinderThread
-    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
+    private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+            @Nullable ImeTracker.Token statsToken) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
@@ -4664,13 +4758,22 @@
             }
             if (!setVisible) {
                 if (mCurClient != null) {
+                    ImeTracker.get().onProgress(statsToken,
+                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+
                     mWindowManagerInternal.hideIme(
                             mHideRequestWindowMap.get(windowToken),
-                            mCurClient.mSelfReportedDisplayId);
+                            mCurClient.mSelfReportedDisplayId, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken,
+                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 }
             } else {
+                ImeTracker.get().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 // Send to window manager to show IME after IME layout finishes.
-                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
+                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken),
+                        statsToken);
             }
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4737,7 +4840,8 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason);
+                hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+                        null /* resultReceiver */, reason);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4754,7 +4858,8 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
-                showCurrentInputLocked(mLastImeTargetWindow, flags, null,
+                showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags,
+                        null /* resultReceiver */,
                         SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -4845,7 +4950,8 @@
             case MSG_HIDE_CURRENT_INPUT_METHOD:
                 synchronized (ImfLock.class) {
                     final @SoftInputShowHideReason int reason = (int) msg.obj;
-                    hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason);
+                    hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
+                            null /* resultReceiver */, reason);
 
                 }
                 return true;
@@ -5297,6 +5403,7 @@
                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
+        notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype);
 
         if (!setSubtypeOnly) {
             // Set InputMethod here
@@ -6332,7 +6439,8 @@
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
                     if (userId == mSettings.getCurrentUserId()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+                        hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+                                0 /* flags */, null /* resultReceiver */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
                         // Reset the current IME
@@ -6597,8 +6705,9 @@
 
         @BinderThread
         @Override
-        public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) {
-            mImms.applyImeVisibility(mToken, windowToken, setVisible);
+        public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
+                @Nullable ImeTracker.Token statsToken) {
+            mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
         }
 
         @BinderThread
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 1435016..b8abd98 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -76,6 +76,8 @@
             "ENABLE_PSDS_PERIODIC_DOWNLOAD";
     private static final String CONFIG_ENABLE_ACTIVE_SIM_EMERGENCY_SUPL =
             "ENABLE_ACTIVE_SIM_EMERGENCY_SUPL";
+    private static final String CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION =
+            "ENABLE_NI_SUPL_MESSAGE_INJECTION";
     static final String CONFIG_LONGTERM_PSDS_SERVER_1 = "LONGTERM_PSDS_SERVER_1";
     static final String CONFIG_LONGTERM_PSDS_SERVER_2 = "LONGTERM_PSDS_SERVER_2";
     static final String CONFIG_LONGTERM_PSDS_SERVER_3 = "LONGTERM_PSDS_SERVER_3";
@@ -218,6 +220,14 @@
     }
 
     /**
+     * Returns true if NI SUPL message injection is enabled; Returns false otherwise.
+     * Default false if not set.
+     */
+    boolean isNiSuplMessageInjectionEnabled() {
+        return getBooleanConfig(CONFIG_ENABLE_NI_SUPL_MESSAGE_INJECTION, false);
+    }
+
+    /**
      * Returns true if a long-term PSDS server is configured.
      */
     boolean isLongTermPsdsServerConfigured() {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 6f637b8..6f6b1c9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -84,6 +84,7 @@
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityGsm;
@@ -95,6 +96,7 @@
 import android.telephony.CellInfoLte;
 import android.telephony.CellInfoNr;
 import android.telephony.CellInfoWcdma;
+import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -107,6 +109,7 @@
 import com.android.internal.location.GpsNetInitiatedHandler;
 import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.HexDump;
 import com.android.server.FgThread;
 import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
 import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
@@ -523,23 +526,31 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
-                if (action == null) {
-                    return;
-                }
+        mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
 
-                switch (action) {
-                    case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
-                    case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
-                        subscriptionOrCarrierConfigChanged();
-                        break;
-                }
+        if (mNetworkConnectivityHandler.isNativeAgpsRilSupported()
+                && mGnssConfiguration.isNiSuplMessageInjectionEnabled()) {
+            // Listen to WAP PUSH NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.3 OMA Push.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+            try {
+                intentFilter.addDataType("application/vnd.omaloc-supl-init");
+            } catch (IntentFilter.MalformedMimeTypeException e) {
+                Log.w(TAG, "Malformed SUPL init mime type");
             }
-        }, intentFilter, null, mHandler);
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+
+            // Listen to MT SMS NI SUPL message.
+            // See User Plane Location Protocol Candidate Version 3.0,
+            // OMA-TS-ULP-V3_0-20110920-C, Section 8.4 MT SMS.
+            intentFilter = new IntentFilter();
+            intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+            intentFilter.addDataScheme("sms");
+            intentFilter.addDataAuthority("localhost", "7275");
+            mContext.registerReceiver(mIntentReceiver, intentFilter, null, mHandler);
+        }
 
         mNetworkConnectivityHandler.registerNetworkCallbacks();
 
@@ -560,6 +571,80 @@
         updateEnabled();
     }
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
+            if (action == null) {
+                return;
+            }
+
+            switch (action) {
+                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+                    subscriptionOrCarrierConfigChanged();
+                    break;
+                case Intents.WAP_PUSH_RECEIVED_ACTION:
+                case Intents.DATA_SMS_RECEIVED_ACTION:
+                    injectSuplInit(intent);
+                    break;
+            }
+        }
+    };
+
+    private void injectSuplInit(Intent intent) {
+        if (!isNfwLocationAccessAllowed()) {
+            Log.w(TAG, "Reject SUPL INIT as no NFW location access");
+            return;
+        }
+
+        int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            Log.e(TAG, "Invalid slot index");
+            return;
+        }
+
+        byte[] suplInit = null;
+        String action = intent.getAction();
+        if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+            SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+            if (messages == null) {
+                Log.e(TAG, "Message does not exist in the intent");
+                return;
+            }
+            for (SmsMessage message : messages) {
+                suplInit = message.getUserData();
+                injectSuplInit(suplInit, slotIndex);
+            }
+        } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+            suplInit = intent.getByteArrayExtra("data");
+            injectSuplInit(suplInit, slotIndex);
+        }
+    }
+
+    private void injectSuplInit(byte[] suplInit, int slotIndex) {
+        if (suplInit != null) {
+            if (DEBUG) {
+                Log.d(TAG, "suplInit = "
+                        + HexDump.toHexString(suplInit) + " slotIndex = " + slotIndex);
+            }
+            mGnssNative.injectNiSuplMessageData(suplInit, suplInit.length , slotIndex);
+        }
+    }
+
+    private boolean isNfwLocationAccessAllowed() {
+        if (mGnssNative.isInEmergencySession()) {
+            return true;
+        }
+        if (mGnssVisibilityControl != null
+                && mGnssVisibilityControl.hasLocationPermissionEnabledProxyApps()) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Implements {@link InjectNtpTimeCallback#injectTime}
      */
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 02bdfd5..a7fffe2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -762,6 +762,10 @@
         return APN_INVALID;
     }
 
+    protected boolean isNativeAgpsRilSupported() {
+        return native_is_agps_ril_supported();
+    }
+
     // AGPS support
     private native void native_agps_data_conn_open(long networkHandle, String apn, int apnIpType);
 
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 631dbbf..4e5e5f8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -437,6 +437,10 @@
         return locationPermissionEnabledProxyApps;
     }
 
+    public boolean hasLocationPermissionEnabledProxyApps() {
+        return getLocationPermissionEnabledProxyApps().length > 0;
+    }
+
     private void handleNfwNotification(NfwNotification nfwNotification) {
         if (DEBUG) Log.d(TAG, "Non-framework location access notification: " + nfwNotification);
 
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 2d015a5d..edb2e5b 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -989,6 +989,14 @@
         mGnssHal.injectPsdsData(data, length, psdsType);
     }
 
+    /**
+     * Injects NI SUPL message data into the GNSS HAL.
+     */
+    public void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+        Preconditions.checkState(mRegistered);
+        mGnssHal.injectNiSuplMessageData(data, length, slotIndex);
+    }
+
     @NativeEntryPoint
     void reportGnssServiceDied() {
         // Not necessary to clear (and restore) binder identity since it runs on another thread.
@@ -1278,7 +1286,7 @@
     }
 
     @NativeEntryPoint
-    boolean isInEmergencySession() {
+    public boolean isInEmergencySession() {
         return Binder.withCleanCallingIdentity(
                 () -> mEmergencyHelper.isInEmergency(
                         TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec())));
@@ -1507,6 +1515,10 @@
         protected void injectPsdsData(byte[] data, int length, int psdsType) {
             native_inject_psds_data(data, length, psdsType);
         }
+
+        protected void injectNiSuplMessageData(byte[] data, int length, int slotIndex) {
+            native_inject_ni_supl_message_data(data, length, slotIndex);
+        }
     }
 
     // basic APIs
@@ -1650,6 +1662,9 @@
     private static native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
             int lac, long cid, int tac, int pcid, int arfcn);
 
+    private static native void native_inject_ni_supl_message_data(byte[] data, int length,
+            int slotIndex);
+
     // PSDS APIs
 
     private static native boolean native_supports_psds();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index d836df5..807ba3c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -309,6 +309,10 @@
     }
 
     private void writeFile(File path, byte[] data) {
+        writeFile(path, data, /* syncParentDir= */ true);
+    }
+
+    private void writeFile(File path, byte[] data, boolean syncParentDir) {
         synchronized (mFileWriteLock) {
             // Use AtomicFile to guarantee atomicity of the file write, including when an existing
             // file is replaced with a new one.  This method is usually used to create new files,
@@ -326,9 +330,11 @@
                 file.failWrite(out);
             }
             // For performance reasons, AtomicFile only syncs the file itself, not also the parent
-            // directory.  The latter must be done explicitly here, as some callers need a guarantee
-            // that the file really exists on-disk when this returns.
-            fsyncDirectory(path.getParentFile());
+            // directory.  The latter must be done explicitly when requested here, as some callers
+            // need a guarantee that the file really exists on-disk when this returns.
+            if (syncParentDir) {
+                fsyncDirectory(path.getParentFile());
+            }
             mCache.putFile(path, data);
         }
     }
@@ -378,10 +384,20 @@
         }
     }
 
+    /**
+     * Writes the synthetic password state file for the given user ID, protector ID, and state name.
+     * If the file already exists, then it is atomically replaced.
+     * <p>
+     * This doesn't sync the parent directory, and a result the new state file may be lost if the
+     * system crashes.  The caller must call {@link syncSyntheticPasswordState()} afterwards to sync
+     * the parent directory if needed, preferably after batching up other state file creations for
+     * the same user.  We do it this way because directory syncs are expensive on some filesystems.
+     */
     public void writeSyntheticPasswordState(int userId, long protectorId, String name,
             byte[] data) {
         ensureSyntheticPasswordDirectoryForUser(userId);
-        writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data);
+        writeFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name), data,
+                /* syncParentDir= */ false);
     }
 
     public byte[] readSyntheticPasswordState(int userId, long protectorId, String name) {
@@ -392,6 +408,13 @@
         deleteFile(getSyntheticPasswordStateFileForUser(userId, protectorId, name));
     }
 
+    /**
+     * Ensures that all synthetic password state files for the user have really been saved to disk.
+     */
+    public void syncSyntheticPasswordState(int userId) {
+        fsyncDirectory(getSyntheticPasswordDirectoryForUser(userId));
+    }
+
     public Map<Integer, List<Long>> listSyntheticPasswordProtectorsForAllUsers(String stateName) {
         Map<Integer, List<Long>> result = new ArrayMap<>();
         final UserManager um = UserManager.get(mContext);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3fd488e..73a16fd 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -619,12 +619,16 @@
 
     /**
      * Creates a new synthetic password (SP) for the given user.
-     *
+     * <p>
      * Any existing SID for the user is cleared.
-     *
+     * <p>
      * Also saves the escrow information necessary to re-generate the synthetic password under
      * an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
      * password escrow should be disabled completely on the given user.
+     * <p>
+     * {@link syncState()} is not called yet; the caller should create a protector afterwards, which
+     * handles this.  This makes it so that all the user's initial SP state files, including the
+     * initial LSKF-based protector, are efficiently created with only a single {@link syncState()}.
      */
     SyntheticPassword newSyntheticPassword(int userId) {
         clearSidForUser(userId);
@@ -668,6 +672,7 @@
 
     private void saveSyntheticPasswordHandle(byte[] spHandle, int userId) {
         saveState(SP_HANDLE_NAME, spHandle, NULL_PROTECTOR_ID, userId);
+        syncState(userId);
     }
 
     private boolean loadEscrowData(SyntheticPassword sp, int userId) {
@@ -677,6 +682,11 @@
         return e0 != null && p1 != null;
     }
 
+    /**
+     * Saves the escrow data for the synthetic password.  The caller is responsible for calling
+     * {@link syncState()} afterwards, once the user's other initial synthetic password state files
+     * have been created.
+     */
     private void saveEscrowData(SyntheticPassword sp, int userId) {
         saveState(SP_E0_NAME, sp.mEncryptedEscrowSplit0, NULL_PROTECTOR_ID, userId);
         saveState(SP_P1_NAME, sp.mEscrowSplit1, NULL_PROTECTOR_ID, userId);
@@ -708,6 +718,10 @@
         return buffer.getInt();
     }
 
+    /**
+     * Creates a file that stores the Weaver slot the protector is using.  The caller is responsible
+     * for calling {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void saveWeaverSlot(int slot, long protectorId, int userId) {
         ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES + Integer.BYTES);
         buffer.put(WEAVER_VERSION);
@@ -837,6 +851,7 @@
         }
         createSyntheticPasswordBlob(protectorId, PROTECTOR_TYPE_LSKF_BASED, sp, protectorSecret,
                 sid, userId);
+        syncState(userId); // ensure the new files are really saved to disk
         return protectorId;
     }
 
@@ -996,6 +1011,7 @@
         saveSecdiscardable(tokenHandle, tokenData.secdiscardableOnDisk, userId);
         createSyntheticPasswordBlob(tokenHandle, getTokenBasedProtectorType(tokenData.mType), sp,
                 tokenData.aggregatedSecret, 0L, userId);
+        syncState(userId); // ensure the new files are really saved to disk
         tokenMap.get(userId).remove(tokenHandle);
         if (tokenData.mCallback != null) {
             tokenData.mCallback.onEscrowTokenActivated(tokenHandle, userId);
@@ -1003,6 +1019,11 @@
         return true;
     }
 
+    /**
+     * Creates a synthetic password blob, i.e. the file that stores the encrypted synthetic password
+     * (or encrypted escrow secret) for a protector.  The caller is responsible for calling
+     * {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void createSyntheticPasswordBlob(long protectorId, byte protectorType,
             SyntheticPassword sp, byte[] protectorSecret, long sid, int userId) {
         final byte[] spSecret;
@@ -1118,6 +1139,7 @@
                             // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
                             pwd.credentialType = credential.getType();
                             saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
+                            syncState(userId);
                             synchronizeFrpPassword(pwd, 0, userId);
                         } else {
                             Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
@@ -1156,6 +1178,7 @@
         if (result.syntheticPassword != null && !credential.isNone() &&
                 !hasPasswordMetrics(protectorId, userId)) {
             savePasswordMetrics(credential, result.syntheticPassword, protectorId, userId);
+            syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
         }
         return result;
     }
@@ -1275,6 +1298,7 @@
                     + blob.mProtectorType);
             createSyntheticPasswordBlob(protectorId, blob.mProtectorType, result, protectorSecret,
                     sid, userId);
+            syncState(userId); // Not strictly needed as the upgrade can be re-done, but be safe.
         }
         return result;
     }
@@ -1396,12 +1420,21 @@
         return ArrayUtils.concat(data, secdiscardable);
     }
 
+    /**
+     * Generates and writes the secdiscardable file for the given protector.  The caller is
+     * responsible for calling {@link syncState()} afterwards, once all the protector's files have
+     * been created.
+     */
     private byte[] createSecdiscardable(long protectorId, int userId) {
         byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
         saveSecdiscardable(protectorId, data, userId);
         return data;
     }
 
+    /**
+     * Writes the secdiscardable file for the given protector.  The caller is responsible for
+     * calling {@link syncState()} afterwards, once all the protector's files have been created.
+     */
     private void saveSecdiscardable(long protectorId, byte[] secdiscardable, int userId) {
         saveState(SECDISCARDABLE_NAME, secdiscardable, protectorId, userId);
     }
@@ -1445,6 +1478,11 @@
         return VersionedPasswordMetrics.deserialize(decrypted).getMetrics();
     }
 
+    /**
+     * Creates the password metrics file: the file associated with the LSKF-based protector that
+     * contains the encrypted metrics about the LSKF.  The caller is responsible for calling
+     * {@link syncState()} afterwards if needed.
+     */
     private void savePasswordMetrics(LockscreenCredential credential, SyntheticPassword sp,
             long protectorId, int userId) {
         final byte[] encrypted = SyntheticPasswordCrypto.encrypt(sp.deriveMetricsKey(),
@@ -1466,10 +1504,21 @@
         return mStorage.readSyntheticPasswordState(userId, protectorId, stateName);
     }
 
+    /**
+     * Persists the given synthetic password state for the given user ID and protector ID.
+     * <p>
+     * For performance reasons, this doesn't sync the user's synthetic password state directory.  As
+     * a result, it doesn't guarantee that the file will really be present after a crash.  If that
+     * is needed, call {@link syncState()} afterwards, preferably after batching up related updates.
+     */
     private void saveState(String stateName, byte[] data, long protectorId, int userId) {
         mStorage.writeSyntheticPasswordState(userId, protectorId, stateName, data);
     }
 
+    private void syncState(int userId) {
+        mStorage.syncSyntheticPasswordState(userId);
+    }
+
     private void destroyState(String stateName, long protectorId, int userId) {
         mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
     }
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bf00a33..5b8ee2b 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -125,6 +125,14 @@
     ActivityInfo getActivityInfo(ComponentName component, long flags, int userId);
 
     /**
+     * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+     * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+     * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+     * chained cross profiles
+     */
+    ActivityInfo getActivityInfoCrossProfile(ComponentName component, long flags, int userId);
+
+    /**
      * Important: The provided filterCallingUid is used exclusively to filter out activities
      * that can be seen based on user state. It's typically the original caller uid prior
      * to clearing. Because it can only be provided by trusted code, its value can be
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b285136..a8534b0 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -835,6 +835,24 @@
     }
 
     /**
+     * Similar to {@link Computer#getActivityInfo(android.content.ComponentName, long, int)} but
+     * only visible as internal service. This method bypass INTERACT_ACROSS_USERS or
+     * INTERACT_ACROSS_USERS_FULL permission checks and only to be used for intent resolution across
+     * chained cross profiles
+     * @param component application's component
+     * @param flags resolve info flags
+     * @param userId user id where activity resides
+     * @return ActivityInfo corresponding to requested component.
+     */
+    public final ActivityInfo getActivityInfoCrossProfile(ComponentName component,
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
+        if (!mUserManager.exists(userId)) return null;
+        flags = updateFlagsForComponent(flags, userId);
+
+        return getActivityInfoInternalBody(component, flags, Binder.getCallingUid(), userId);
+    }
+
+    /**
      * Important: The provided filterCallingUid is used exclusively to filter out activities
      * that can be seen based on user state. It's typically the original caller uid prior
      * to clearing. Because it can only be provided by trusted code, its value can be
@@ -1711,7 +1729,7 @@
         ComponentName forwardingActivityComponentName = new ComponentName(
                 androidApplication().packageName, className);
         ActivityInfo forwardingActivityInfo =
-                getActivityInfo(forwardingActivityComponentName, 0,
+                getActivityInfoCrossProfile(forwardingActivityComponentName, 0,
                         sourceUserId);
         if (!targetIsProfile) {
             forwardingActivityInfo.showUserIcon = targetUserId;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 798217f..04bd135 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -49,6 +49,15 @@
     //flag to decide if intent needs to be resolved cross profile if pkgName is already defined
     public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
 
+    /*
+    This flag, denotes if further cross profile resolution is allowed, e.g. if profile#0 is linked
+    to profile#1 and profile#2 . When intent resolution from profile#1 is started we resolve it in
+    profile#1 and profile#0. The profile#0 is also linked to profile#2, we will only resolve in
+    profile#2 if CrossProfileIntentFilter between profile#1 and profile#0 have set flag
+    FLAG_ALLOW_CHAINED_RESOLUTION.
+     */
+    public static final int FLAG_ALLOW_CHAINED_RESOLUTION = 0x00000010;
+
     private static final String TAG = "CrossProfileIntentFilter";
 
     /**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 5ae4cab..4362956 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -36,14 +36,19 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.server.LocalServices;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationUtils;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Queue;
+import java.util.Set;
 import java.util.function.Function;
 
 /**
@@ -115,73 +120,111 @@
             Intent intent, String resolvedType, int userId, long flags, String pkgName,
             boolean hasNonNegativePriorityResult,
             Function<String, PackageStateInternal> pkgSettingFunction) {
-
+        Queue<Integer> pendingUsers = new ArrayDeque<>();
+        Set<Integer> visitedUserIds = new HashSet<>();
+        SparseBooleanArray hasNonNegativePriorityResultFromParent = new SparseBooleanArray();
+        visitedUserIds.add(userId);
+        pendingUsers.add(userId);
+        hasNonNegativePriorityResultFromParent.put(userId, hasNonNegativePriorityResult);
+        UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+        while (!pendingUsers.isEmpty()) {
+            int currentUserId = pendingUsers.poll();
+            List<CrossProfileIntentFilter> matchingFilters =
+                    computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+                            currentUserId);
 
-        List<CrossProfileIntentFilter> matchingFilters =
-                computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
-
-        if (matchingFilters == null || matchingFilters.isEmpty()) {
-            /** if intent is web intent, checking if parent profile should handle the intent even
-            if there is no matching filter. The configuration is based on user profile
-            restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
-            if (intent.hasWebURI()) {
-                UserInfo parent = computer.getProfileParent(userId);
-                if (parent != null) {
-                    CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
-                            .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, userId,
-                                    parent.id);
-                    if (generalizedCrossProfileDomainInfo != null) {
-                        crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+            if (matchingFilters == null || matchingFilters.isEmpty()) {
+                /** if intent is web intent, checking if parent profile should handle the intent
+                 * even if there is no matching filter. The configuration is based on user profile
+                 * restriction android.os.UserManager#ALLOW_PARENT_PROFILE_APP_LINKING **/
+                if (currentUserId == userId && intent.hasWebURI()) {
+                    UserInfo parent = computer.getProfileParent(currentUserId);
+                    if (parent != null) {
+                        CrossProfileDomainInfo generalizedCrossProfileDomainInfo = computer
+                                .getCrossProfileDomainPreferredLpr(intent, resolvedType, flags,
+                                        currentUserId, parent.id);
+                        if (generalizedCrossProfileDomainInfo != null) {
+                            crossProfileDomainInfos.add(generalizedCrossProfileDomainInfo);
+                        }
                     }
                 }
+                continue;
             }
-            return crossProfileDomainInfos;
-        }
 
-        UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-        UserInfo sourceUserInfo = umInternal.getUserInfo(userId);
+            UserInfo sourceUserInfo = umInternal.getUserInfo(currentUserId);
 
-       // Grouping the CrossProfileIntentFilters based on targerId
-        SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
-                new SparseArray<>();
+            // Grouping the CrossProfileIntentFilters based on targerId
+            SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
+                    new SparseArray<>();
 
-        for (int index = 0; index < matchingFilters.size(); index++) {
-            CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
+            for (int index = 0; index < matchingFilters.size(); index++) {
+                CrossProfileIntentFilter crossProfileIntentFilter = matchingFilters.get(index);
 
-            if (!crossProfileIntentFiltersByUser
-                    .contains(crossProfileIntentFilter.mTargetUserId)) {
-                crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
-                        new ArrayList<>());
+                if (!crossProfileIntentFiltersByUser
+                        .contains(crossProfileIntentFilter.mTargetUserId)) {
+                    crossProfileIntentFiltersByUser.put(crossProfileIntentFilter.mTargetUserId,
+                            new ArrayList<>());
+                }
+                crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
+                        .add(crossProfileIntentFilter);
             }
-            crossProfileIntentFiltersByUser.get(crossProfileIntentFilter.mTargetUserId)
-                    .add(crossProfileIntentFilter);
-        }
 
-        /*
-         For each target user, we would call their corresponding strategy
-         {@link CrossProfileResolver} to resolve intent in corresponding user
-         */
-        for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
+            /*
+             For each target user, we would call their corresponding strategy
+             {@link CrossProfileResolver} to resolve intent in corresponding user
+             */
+            for (int index = 0; index < crossProfileIntentFiltersByUser.size(); index++) {
 
-            UserInfo targetUserInfo = umInternal.getUserInfo(crossProfileIntentFiltersByUser
-                    .keyAt(index));
+                int targetUserId = crossProfileIntentFiltersByUser.keyAt(index);
 
-            // Choosing strategy based on source and target user
-            CrossProfileResolver crossProfileResolver =
-                    chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
+                //if user is already visited then skip resolution for particular user.
+                if (visitedUserIds.contains(targetUserId)) {
+                    continue;
+                }
+
+                UserInfo targetUserInfo = umInternal.getUserInfo(targetUserId);
+
+                // Choosing strategy based on source and target user
+                CrossProfileResolver crossProfileResolver =
+                        chooseCrossProfileResolver(computer, sourceUserInfo, targetUserInfo);
 
             /*
             If {@link CrossProfileResolver} is available for source,target pair we will call it to
             get {@link CrossProfileDomainInfo}s from that user.
              */
-            if (crossProfileResolver != null) {
-                List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
-                        .resolveIntent(computer, intent, resolvedType, userId,
-                                crossProfileIntentFiltersByUser.keyAt(index), flags, pkgName,
-                                crossProfileIntentFiltersByUser.valueAt(index),
-                                hasNonNegativePriorityResult, pkgSettingFunction);
-                crossProfileDomainInfos.addAll(crossProfileInfos);
+                if (crossProfileResolver != null) {
+                    List<CrossProfileDomainInfo> crossProfileInfos = crossProfileResolver
+                            .resolveIntent(computer, intent, resolvedType, currentUserId,
+                                    targetUserId, flags, pkgName,
+                                    crossProfileIntentFiltersByUser.valueAt(index),
+                                    hasNonNegativePriorityResultFromParent.get(currentUserId),
+                                    pkgSettingFunction);
+                    crossProfileDomainInfos.addAll(crossProfileInfos);
+
+                    hasNonNegativePriorityResultFromParent.put(targetUserId,
+                            hasNonNegativePriority(crossProfileInfos));
+
+                    /*
+                    Adding target user to queue if flag
+                    {@link CrossProfileIntentFilter#FLAG_ALLOW_CHAINED_RESOLUTION} is set for any
+                    {@link CrossProfileIntentFilter}
+                     */
+                    boolean allowChainedResolution = false;
+                    for (int filterIndex = 0; filterIndex < crossProfileIntentFiltersByUser
+                            .valueAt(index).size(); filterIndex++) {
+                        if ((CrossProfileIntentFilter
+                                .FLAG_ALLOW_CHAINED_RESOLUTION & crossProfileIntentFiltersByUser
+                                .valueAt(index).get(filterIndex).mFlags) != 0) {
+                            allowChainedResolution = true;
+                            break;
+                        }
+                    }
+                    if (allowChainedResolution) {
+                        pendingUsers.add(targetUserId);
+                    }
+                    visitedUserIds.add(targetUserId);
+                }
             }
         }
 
@@ -237,7 +280,7 @@
 
     /**
      * Returns true if we source user can reach target user for given intent. The source can
-     * directly or indirectly reach to target. This will perform depth first search to check if
+     * directly or indirectly reach to target. This will perform breadth first search to check if
      * source can reach target.
      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
      * @param intent request
@@ -251,13 +294,38 @@
             @UserIdInt int targetUserId) {
         if (sourceUserId == targetUserId) return true;
 
-        List<CrossProfileIntentFilter> matches =
-                computer.getMatchingCrossProfileIntentFilters(intent, resolvedType, sourceUserId);
-        if (matches != null) {
-            for (int index = 0; index < matches.size(); index++) {
-                CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
-                if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
-                    return true;
+        Queue<Integer> pendingUsers = new ArrayDeque<>();
+        Set<Integer> visitedUserIds = new HashSet<>();
+        visitedUserIds.add(sourceUserId);
+        pendingUsers.add(sourceUserId);
+
+        while (!pendingUsers.isEmpty()) {
+            int currentUserId = pendingUsers.poll();
+
+            List<CrossProfileIntentFilter> matches =
+                    computer.getMatchingCrossProfileIntentFilters(intent, resolvedType,
+                            currentUserId);
+            if (matches != null) {
+                for (int index = 0; index < matches.size(); index++) {
+                    CrossProfileIntentFilter crossProfileIntentFilter = matches.get(index);
+                    if (crossProfileIntentFilter.mTargetUserId == targetUserId) {
+                        return true;
+                    }
+                    if (visitedUserIds.contains(crossProfileIntentFilter.mTargetUserId)) {
+                        continue;
+                    }
+
+                    /*
+                     If source cannot directly reach to target, we will add
+                     CrossProfileIntentFilter.mTargetUserId user to queue to check if target user
+                     can be reached via CrossProfileIntentFilter.mTargetUserId i.e. it can be
+                     indirectly reached through chained/linked profiles.
+                     */
+                    if ((CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION
+                            & crossProfileIntentFilter.mFlags) != 0) {
+                        pendingUsers.add(crossProfileIntentFilter.mTargetUserId);
+                        visitedUserIds.add(crossProfileIntentFilter.mTargetUserId);
+                    }
                 }
             }
         }
@@ -605,4 +673,14 @@
 
         return resolveInfoList;
     }
+
+    /**
+     * @param crossProfileDomainInfos list of cross profile domain info in descending priority order
+     * @return if the list contains a resolve info with non-negative priority
+     */
+    private boolean hasNonNegativePriority(List<CrossProfileDomainInfo> crossProfileDomainInfos) {
+        return crossProfileDomainInfos.size() > 0
+                && crossProfileDomainInfos.get(0).mResolveInfo != null
+                && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index cac9323..ceaaefd 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -319,4 +319,135 @@
                 HOME,
                 MOBILE_NETWORK_SETTINGS);
     }
+
+    /**
+     * Clone profile's DefaultCrossProfileIntentFilter
+     */
+
+    /*
+     Allowing media capture from clone to parent profile as clone profile would not have camera
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_MEDIA_CAPTURE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+                    .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
+                    .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+                    .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /*
+     Allowing send action from clone to parent profile to share content from clone apps to parent
+     apps
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_SEND_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_SEND)
+                    .addAction(Intent.ACTION_SEND_MULTIPLE)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing send action from parent to clone profile to share content from parent apps to clone
+     apps
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_SEND_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_SEND)
+                    .addAction(Intent.ACTION_SEND_MULTIPLE)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing view action from clone to parent profile to open any app-links or web links
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_VIEW_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                    // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addDataScheme("https")
+                    .addDataScheme("http")
+                    .build();
+
+    /*
+     Allowing view action from parent to clone profile to open any app-links or web links
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_VIEW_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addDataScheme("https")
+                    .addDataScheme("http")
+                    .build();
+
+    /*
+     Allowing pick,insert and edit action from clone to parent profile to open picker or contacts
+     insert/edit.
+     */
+    private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_PICK_INSERT_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_PICK)
+                    .addAction(Intent.ACTION_GET_CONTENT)
+                    .addAction(Intent.ACTION_EDIT)
+                    .addAction(Intent.ACTION_INSERT)
+                    .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addDataType("*/*")
+                    .build();
+
+    /*
+     Allowing pick,insert and edit action from parent to clone profile to open picker
+     */
+    private static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_PICK_INSERT_ACTION =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */ 0x00000018, // 0x00000018 means FLAG_IS_PACKAGE_FOR_FILTER
+                                            // and FLAG_ALLOW_CHAINED_RESOLUTION set
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_PICK)
+                    .addAction(Intent.ACTION_GET_CONTENT)
+                    .addAction(Intent.ACTION_EDIT)
+                    .addAction(Intent.ACTION_INSERT)
+                    .addAction(Intent.ACTION_INSERT_OR_EDIT)
+                    .addDataType("*/*")
+                    .build();
+
+    public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
+        return Arrays.asList(
+                PARENT_TO_CLONE_SEND_ACTION,
+                PARENT_TO_CLONE_VIEW_ACTION,
+                PARENT_TO_CLONE_PICK_INSERT_ACTION,
+                CLONE_TO_PARENT_MEDIA_CAPTURE,
+                CLONE_TO_PARENT_SEND_ACTION,
+                CLONE_TO_PARENT_VIEW_ACTION,
+                CLONE_TO_PARENT_PICK_INSERT_ACTION
+
+        );
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7dae4c6..2a0b44d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -150,7 +150,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -875,7 +874,7 @@
                 }
             }
 
-            Map<String, ReconciledPackage> reconciledPackages;
+            List<ReconciledPackage> reconciledPackages;
             synchronized (mPm.mLock) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
@@ -1883,11 +1882,11 @@
     }
 
     @GuardedBy("mPm.mLock")
-    private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+    private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
             @NonNull int[] allUsers) {
         // TODO: remove any expected failures from this method; this should only be able to fail due
         //       to unavoidable errors (I/O, etc.)
-        for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+        for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final ParsedPackage parsedPackage = installRequest.getParsedPackage();
             final String packageName = parsedPackage.getPackageName();
@@ -2205,9 +2204,9 @@
      * locks on {@link com.android.server.pm.PackageManagerService.mLock}.
      */
     @GuardedBy("mPm.mInstallLock")
-    private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
+    private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+        for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
             final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
@@ -2338,45 +2337,6 @@
                 incrementalStorages);
     }
 
-    public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
-        String packageName = pkgLite.packageName;
-        int installLocation = pkgLite.installLocation;
-        // reader
-        synchronized (mPm.mLock) {
-            // Currently installed package which the new package is attempting to replace or
-            // null if no such package is installed.
-            AndroidPackage installedPkg = mPm.mPackages.get(packageName);
-
-            if (installedPkg != null) {
-                if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                    // Check for updated system application.
-                    if (installedPkg.isSystem()) {
-                        return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                    } else {
-                        // If current upgrade specifies particular preference
-                        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
-                            // Application explicitly specified internal.
-                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                        } else if (
-                                installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
-                            // App explicitly prefers external. Let policy decide
-                        } else {
-                            // Prefer previous location
-                            if (installedPkg.isExternalStorage()) {
-                                return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL;
-                            }
-                            return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL;
-                        }
-                    }
-                } else {
-                    // Invalid install. Return error code
-                    return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS;
-                }
-            }
-        }
-        return pkgLite.recommendedInstallLocation;
-    }
-
     Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
             long requiredInstalledVersionCode, int installFlags) {
         if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
@@ -3645,7 +3605,7 @@
             boolean appIdCreated = false;
             try {
                 final String pkgName = scanResult.mPkgSetting.getPackageName();
-                final Map<String, ReconciledPackage> reconcileResult =
+                final List<ReconciledPackage> reconcileResult =
                         ReconcilePackageUtils.reconcilePackages(
                                 Collections.singletonList(installRequest),
                                 mPm.mPackages, Collections.singletonMap(pkgName,
@@ -3657,7 +3617,7 @@
                 } else {
                     installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
                 }
-                commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+                commitReconciledScanResultLocked(reconcileResult.get(0),
                         mPm.mUserManager.getUserIds());
             } catch (PackageManagerException e) {
                 if (appIdCreated) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 4443710..01a8bd0 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -59,8 +59,10 @@
     @Nullable
     private PackageRemovedInfo mRemovedInfo;
 
-    private @PackageManagerService.ScanFlags int mScanFlags;
-    private @ParsingPackageUtils.ParseFlags int mParseFlags;
+    @PackageManagerService.ScanFlags
+    private int mScanFlags;
+    @ParsingPackageUtils.ParseFlags
+    private int mParseFlags;
     private boolean mReplace;
 
     @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
@@ -155,7 +157,7 @@
         mParseFlags = parseFlags;
         mScanFlags = scanFlags;
         mScanResult = scanResult;
-        mPackageMetrics = null; // No real logging from this code path
+        mPackageMetrics = null; // No logging from this code path
     }
 
     @Nullable
@@ -393,11 +395,13 @@
         return mParsedPackage;
     }
 
-    public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+    @ParsingPackageUtils.ParseFlags
+    public int getParseFlags() {
         return mParseFlags;
     }
 
-    public @PackageManagerService.ScanFlags int getScanFlags() {
+    @PackageManagerService.ScanFlags
+    public int getScanFlags() {
         return mScanFlags;
     }
 
@@ -435,6 +439,11 @@
         return mIsInstallForUsers;
     }
 
+    public boolean isInstallFromAdb() {
+        return mInstallArgs != null
+                && (mInstallArgs.mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0;
+    }
+
     @Nullable
     public PackageSetting getOriginalPackageSetting() {
         return mOriginalPs;
@@ -731,10 +740,10 @@
         }
     }
 
-    public void onInstallCompleted() {
+    public void onInstallCompleted(Computer snapshot) {
         if (getReturnCode() == INSTALL_SUCCEEDED) {
             if (mPackageMetrics != null) {
-                mPackageMetrics.onInstallSucceed();
+                mPackageMetrics.onInstallSucceed(snapshot);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index d13822a..e4a0a3a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -510,7 +510,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                request.onInstallCompleted();
+                request.onInstallCompleted(mPm.snapshotComputer());
                 doPostInstall(request);
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index b725325..0391163 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -16,16 +16,26 @@
 
 package com.android.server.pm;
 
+import static android.os.Process.INVALID_UID;
+
 import android.annotation.IntDef;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.UserManager;
 import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.pkg.PackageStateInternal;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
 
 /**
  * Metrics class for reporting stats to logging infrastructures like Westworld
@@ -43,7 +53,8 @@
             STEP_COMMIT,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface StepInt {}
+    public @interface StepInt {
+    }
 
     private final long mInstallStartTimestampMillis;
     private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
@@ -56,16 +67,30 @@
         mInstallRequest = installRequest;
     }
 
-    public void onInstallSucceed() {
+    public void onInstallSucceed(Computer snapshot) {
+        // TODO(b/239722919): report to SecurityLog if on work profile or managed device
+        reportInstallationStats(snapshot, true /* success */);
+    }
+
+    private void reportInstallationStats(Computer snapshot, boolean success) {
+        // TODO(b/249294752): do not log if adb
         final long installDurationMillis =
                 System.currentTimeMillis() - mInstallStartTimestampMillis;
         // write to stats
         final Pair<int[], long[]> stepDurations = getInstallStepDurations();
         final int[] newUsers = mInstallRequest.getNewUsers();
         final int[] originalUsers = mInstallRequest.getOriginUsers();
+        final String packageName = mInstallRequest.getName();
+        final String installerPackageName = mInstallRequest.getInstallerPackageName();
+        final int installerUid = installerPackageName == null ? INVALID_UID
+                : snapshot.getPackageUid(installerPackageName, 0, 0);
+        final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+        final long versionCode = success ? 0 : ps.getVersionCode();
+        final long apksSize = getApksSize(ps.getPath());
+
         FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
                 0 /* session_id */,
-                null /* package_name */,
+                success ? null : packageName /* not report package_name on success */,
                 mInstallRequest.getUid() /* uid */,
                 newUsers /* user_ids */,
                 getUserTypes(newUsers) /* user_types */,
@@ -73,13 +98,13 @@
                 getUserTypes(originalUsers) /* original_user_types */,
                 mInstallRequest.getReturnCode() /* public_return_code */,
                 0 /* internal_error_code */,
-                0 /* apks_size_bytes */,
-                0 /* version_code */,
+                apksSize /* apks_size_bytes */,
+                versionCode /* version_code */,
                 stepDurations.first /* install_steps */,
                 stepDurations.second /* step_duration_millis */,
                 installDurationMillis /* total_duration_millis */,
                 mInstallRequest.getInstallFlags() /* install_flags */,
-                -1 /* installer_package_uid */,
+                installerUid /* installer_package_uid */,
                 -1 /* original_installer_package_uid */,
                 mInstallRequest.getDataLoaderType() /* data_loader_type */,
                 0 /* user_action_required_type */,
@@ -93,6 +118,19 @@
         );
     }
 
+    private long getApksSize(File apkDir) {
+        // TODO(b/249294752): also count apk sizes for failed installs
+        final AtomicLong apksSize = new AtomicLong();
+        try (Stream<Path> walkStream = Files.walk(apkDir.toPath())) {
+            walkStream.filter(p -> p.toFile().isFile()
+                    && ApkLiteParseUtils.isApkFile(p.toFile())).forEach(
+                            f -> apksSize.addAndGet(f.toFile().length()));
+        } catch (IOException e) {
+            // ignore
+        }
+        return apksSize.get();
+    }
+
     public void onStepStarted(@StepInt int step) {
         mInstallSteps.put(step, new InstallStep());
     }
@@ -140,12 +178,15 @@
     private static class InstallStep {
         private final long mStartTimestampMillis;
         private long mDurationMillis = -1;
+
         InstallStep() {
             mStartTimestampMillis = System.currentTimeMillis();
         }
+
         void finish() {
             mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
         }
+
         long getDurationMillis() {
             return mDurationMillis;
         }
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 01d17f6..99bcbc9 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -35,6 +35,7 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -48,14 +49,14 @@
  * as install) led to the request.
  */
 final class ReconcilePackageUtils {
-    public static Map<String, ReconciledPackage> reconcilePackages(
+    public static List<ReconciledPackage> reconcilePackages(
             List<InstallRequest> installRequests,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
             KeySetManagerService ksms, Settings settings)
             throws ReconcileFailure {
-        final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
+        final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
         // make a copy of the existing set of packages so we can combine them with incoming packages
         final ArrayMap<String, AndroidPackage> combinedPackages =
@@ -88,7 +89,6 @@
             }
 
 
-
             final DeletePackageAction deletePackageAction;
             // we only want to try to delete for non system apps
             if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
@@ -257,13 +257,11 @@
                 }
             }
 
-            result.put(installPackageName,
+            final ReconciledPackage reconciledPackage =
                     new ReconciledPackage(installRequests, allPackages, installRequest,
                             deletePackageAction, allowedSharedLibInfos, signingDetails,
-                            sharedUserSignaturesChanged, removeAppKeySetData));
-        }
+                            sharedUserSignaturesChanged, removeAppKeySetData);
 
-        for (InstallRequest installRequest : installRequests) {
             // Check all shared libraries and map to their actual file path.
             // We only do this here for apps not on a system dir, because those
             // are the only ones that can fail an install due to this.  We
@@ -271,24 +269,21 @@
             // library paths after the scan is done. Also during the initial
             // scan don't update any libs as we do this wholesale after all
             // apps are scanned to avoid dependency based scanning.
-            if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
-                    || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
-                    != 0) {
-                continue;
+            if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0
+                    && (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    == 0) {
+                try {
+                    reconciledPackage.mCollectedSharedLibraryInfos =
+                            sharedLibraries.collectSharedLibraryInfos(
+                                    installRequest.getParsedPackage(), combinedPackages,
+                                    incomingSharedLibraries);
+                } catch (PackageManagerException e) {
+                    throw new ReconcileFailure(e.error, e.getMessage());
+                }
             }
-            final String installPackageName = installRequest.getParsedPackage().getPackageName();
-            try {
-                result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        sharedLibraries.collectSharedLibraryInfos(
-                                installRequest.getParsedPackage(), combinedPackages,
-                                incomingSharedLibraries);
-            } catch (PackageManagerException e) {
-                throw new ReconcileFailure(e.error, e.getMessage());
-            }
-        }
 
-        for (InstallRequest installRequest : installRequests) {
             installRequest.onReconcileFinished();
+            result.add(reconciledPackage);
         }
 
         return result;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d255669..1c50f38 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 
@@ -2814,7 +2815,8 @@
         synchronized (mUsersLock) {
             count = getAliveUsersExcludingGuestsCountLU();
         }
-        return count >= UserManager.getMaxSupportedUsers();
+        return count >= UserManager.getMaxSupportedUsers()
+                && !isCreationOverrideEnabled();
     }
 
     /**
@@ -2824,15 +2826,16 @@
      * <p>For checking whether more profiles can be added to a particular parent use
      * {@link #canAddMoreProfilesToUser}.
      */
-    private boolean canAddMoreUsersOfType(UserTypeDetails userTypeDetails) {
-        if (!userTypeDetails.isEnabled()) {
+    private boolean canAddMoreUsersOfType(@NonNull UserTypeDetails userTypeDetails) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             return false;
         }
         final int max = userTypeDetails.getMaxAllowed();
         if (max == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS) {
             return true; // Indicates that there is no max.
         }
-        return getNumberOfUsersOfType(userTypeDetails.getName()) < max;
+        return getNumberOfUsersOfType(userTypeDetails.getName()) < max
+                || isCreationOverrideEnabled();
     }
 
     /**
@@ -2843,7 +2846,7 @@
     public int getRemainingCreatableUserCount(String userType) {
         checkQueryOrCreateUsersPermission("get the remaining number of users that can be added.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         synchronized (mUsersLock) {
@@ -2917,7 +2920,21 @@
     public boolean isUserTypeEnabled(String userType) {
         checkCreateUsersPermission("check if user type is enabled.");
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
-        return userTypeDetails != null && userTypeDetails.isEnabled();
+        return userTypeDetails != null && isUserTypeEnabled(userTypeDetails);
+    }
+
+    /** Returns whether the creation of users of the given user type is enabled on this device. */
+    private boolean isUserTypeEnabled(@NonNull UserTypeDetails userTypeDetails) {
+        return userTypeDetails.isEnabled() || isCreationOverrideEnabled();
+    }
+
+    /**
+     * Returns whether to almost-always allow creating users even beyond their limit or if disabled.
+     * For Debug builds only.
+     */
+    private boolean isCreationOverrideEnabled() {
+        return Build.isDebuggable()
+                && SystemProperties.getBoolean(DEV_CREATE_OVERRIDE_PROPERTY, false);
     }
 
     @Override
@@ -2930,7 +2947,8 @@
     @Override
     public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
             boolean allowedToRemoveOne) {
-        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne);
+        return 0 < getRemainingCreatableProfileCount(userType, userId, allowedToRemoveOne)
+                || isCreationOverrideEnabled();
     }
 
     @Override
@@ -2948,7 +2966,7 @@
         checkQueryOrCreateUsersPermission(
                 "get the remaining number of profiles that can be added to the given user.");
         final UserTypeDetails type = mUserTypes.get(userType);
-        if (type == null || !type.isEnabled()) {
+        if (type == null || !isUserTypeEnabled(type)) {
             return 0;
         }
         // Managed profiles have their own specific rules.
@@ -4407,7 +4425,7 @@
                     + ") indicated SYSTEM user, which cannot be created.");
             return null;
         }
-        if (!userTypeDetails.isEnabled()) {
+        if (!isUserTypeEnabled(userTypeDetails)) {
             throwCheckedUserOperationException(
                     "Cannot add a user of disabled type " + userType + ".",
                     UserManager.USER_OPERATION_ERROR_MAX_USERS);
@@ -4479,27 +4497,12 @@
                                     + " for user " + parentId,
                             UserManager.USER_OPERATION_ERROR_MAX_USERS);
                 }
-                // In legacy mode, restricted profile's parent can only be the owner user
-                if (isRestricted && !UserManager.isSplitSystemUser()
-                        && (parentId != UserHandle.USER_SYSTEM)) {
+                if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
+                        && !isCreationOverrideEnabled()) {
                     throwCheckedUserOperationException(
-                            "Cannot add restricted profile - parent user must be owner",
+                            "Cannot add restricted profile - parent user must be system",
                             UserManager.USER_OPERATION_ERROR_UNKNOWN);
                 }
-                if (isRestricted && UserManager.isSplitSystemUser()) {
-                    if (parent == null) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - parent user must be specified",
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                    if (!parent.info.canHaveProfile()) {
-                        throwCheckedUserOperationException(
-                                "Cannot add restricted profile - profiles cannot be created for "
-                                        + "the specified parent user id "
-                                        + parentId,
-                                UserManager.USER_OPERATION_ERROR_UNKNOWN);
-                    }
-                }
 
                 userId = getNextAvailableId();
                 Slog.i(LOG_TAG, "Creating user " + userId + " of type " + userType);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index c35fe17..871420a9 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -126,11 +126,13 @@
                 .setCrossProfileIntentFilterAccessControl(
                         CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
                 .setIsCredentialSharableWithParent(true)
+                .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter())
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT)
-                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+                        .setUseParentsContacts(true));
     }
 
     /**
@@ -310,6 +312,10 @@
         return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
     }
 
+    private static List<DefaultCrossProfileIntentFilter> getDefaultCloneCrossProfileIntentFilter() {
+        return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
+    }
+
     /**
      * Reads the given xml parser to obtain device user-type customization, and updates the given
      * map of {@link UserTypeDetails.Builder}s accordingly.
@@ -391,6 +397,7 @@
                 }
 
                 setIntAttribute(parser, "enabled", builder::setEnabled);
+                setIntAttribute(parser, "max-allowed", builder::setMaxAllowed);
 
                 // Process child elements.
                 final int depth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8588267..28f86ed 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -616,6 +616,10 @@
         grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId,
                 NOTIFICATION_PERMISSIONS);
 
+        // Dock Manager
+        grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId,
+                NOTIFICATION_PERMISSIONS);
+
         // Camera
         grantPermissionsToSystemPackage(pm,
                 getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId),
@@ -933,6 +937,10 @@
         return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName);
     }
 
+    private String getDefaultDockManagerPackage() {
+        return mContext.getString(R.string.config_defaultDockManagerPackageName);
+    }
+
     @SafeVarargs
     private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
             ArrayList<String> packages, int userId, Set<String>... permissions) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d8b1120..cc84c85 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1053,7 +1053,7 @@
         super(context);
 
         mContext = context;
-        mBinderService = new BinderService();
+        mBinderService = new BinderService(mContext);
         mLocalService = new LocalService();
         mNativeWrapper = injector.createNativeWrapper();
         mSystemProperties = injector.createSystemPropertiesWrapper();
@@ -5485,12 +5485,17 @@
 
     @VisibleForTesting
     final class BinderService extends IPowerManager.Stub {
+        private final PowerManagerShellCommand mShellCommand;
+
+        BinderService(Context context) {
+            mShellCommand = new PowerManagerShellCommand(context, this);
+        }
+
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            (new PowerManagerShellCommand(this)).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index a9b33ed..9439b76 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,10 +16,15 @@
 
 package com.android.server.power;
 
+import android.content.Context;
 import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.util.SparseArray;
+import android.view.Display;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -27,9 +32,13 @@
 class PowerManagerShellCommand extends ShellCommand {
     private static final int LOW_POWER_MODE_ON = 1;
 
-    final PowerManagerService.BinderService mService;
+    private final Context mContext;
+    private final PowerManagerService.BinderService mService;
 
-    PowerManagerShellCommand(PowerManagerService.BinderService service) {
+    private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
+
+    PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
+        mContext = context;
         mService = service;
     }
 
@@ -52,6 +61,8 @@
                     return runSuppressAmbientDisplay();
                 case "list-ambient-display-suppression-tokens":
                     return runListAmbientDisplaySuppressionTokens();
+                case "set-prox":
+                    return runSetProx();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -117,6 +128,56 @@
 
         return 0;
     }
+
+    /** TODO: Consider updating this code to support all wakelock types. */
+    private int runSetProx() throws RemoteException {
+        PrintWriter pw = getOutPrintWriter();
+        final boolean acquire;
+        switch (getNextArgRequired().toLowerCase()) {
+            case "list":
+                pw.println("Wakelocks:");
+                pw.println(mProxWakelocks);
+                return 0;
+            case "acquire":
+                acquire = true;
+                break;
+            case "release":
+                acquire = false;
+                break;
+            default:
+                pw.println("Error: Allowed options are 'list' 'enable' and 'disable'.");
+                return -1;
+        }
+
+        int displayId = Display.INVALID_DISPLAY;
+        String displayOption = getNextArg();
+        if ("-d".equals(displayOption)) {
+            String idStr = getNextArg();
+            displayId = Integer.parseInt(idStr);
+            if (displayId < 0) {
+                pw.println("Error: Specified displayId (" + idStr + ") must a non-negative int.");
+                return -1;
+            }
+        }
+
+        int wakelockIndex = displayId + 1; // SparseArray doesn't support negative indexes
+        WakeLock wakelock = mProxWakelocks.get(wakelockIndex);
+        if (wakelock == null) {
+            PowerManager pm = mContext.getSystemService(PowerManager.class);
+            wakelock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+                        "PowerManagerShellCommand[" + displayId + "]", displayId);
+            mProxWakelocks.put(wakelockIndex, wakelock);
+        }
+
+        if (acquire) {
+            wakelock.acquire();
+        } else {
+            wakelock.release();
+        }
+        pw.println(wakelock);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -138,6 +199,11 @@
         pw.println("    ambient display");
         pw.println("  list-ambient-display-suppression-tokens");
         pw.println("    prints the tokens used to suppress ambient display");
+        pw.println("  set-prox [list|acquire|release] (-d <display_id>)");
+        pw.println("    Acquires the proximity sensor wakelock. Wakelock is associated with");
+        pw.println("    a specific display if specified. 'list' lists wakelocks previously");
+        pw.println("    created by set-prox including their held status.");
+
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7386a19..3b7bf48 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1211,7 +1211,8 @@
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
                 if (r != null) {
-                    r.reportFullyDrawnLocked(restoredFromBundle);
+                    mTaskSupervisor.getActivityMetricsLogger().notifyFullyDrawn(r,
+                            restoredFromBundle);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index f0de1d3..e1ab291 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -454,6 +454,7 @@
         final int windowsFullyDrawnDelayMs;
         final int activityRecordIdHashCode;
         final boolean relaunched;
+        final long timestampNs;
 
         private TransitionInfoSnapshot(TransitionInfo info) {
             this(info, info.mLastLaunchedActivity, INVALID_DELAY);
@@ -483,6 +484,7 @@
             activityRecordIdHashCode = System.identityHashCode(launchedActivity);
             this.windowsFullyDrawnDelayMs = windowsFullyDrawnDelayMs;
             relaunched = info.mRelaunched;
+            timestampNs = info.mLaunchingState.mStartRealtimeNs;
         }
 
         @WaitResult.LaunchState int getLaunchState() {
@@ -498,6 +500,10 @@
             }
         }
 
+        boolean isIntresetedToEventLog() {
+            return type == TYPE_TRANSITION_WARM_LAUNCH || type == TYPE_TRANSITION_COLD_LAUNCH;
+        }
+
         PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
             return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
                     ? PackageOptimizationInfo.createWithNoInfo()
@@ -1022,16 +1028,17 @@
         // This will avoid any races with other operations that modify the ActivityRecord.
         final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
         if (info.isInterestingToLoggerAndObserver()) {
-            final long timestampNs = info.mLaunchingState.mStartRealtimeNs;
             final long uptimeNs = info.mLaunchingState.mStartUptimeNs;
             final int transitionDelay = info.mCurrentTransitionDelayMs;
             final int processState = info.mProcessState;
             final int processOomAdj = info.mProcessOomAdj;
             mLoggerHandler.post(() -> logAppTransition(
-                    timestampNs, uptimeNs, transitionDelay, infoSnapshot, isHibernating,
+                    uptimeNs, transitionDelay, infoSnapshot, isHibernating,
                     processState, processOomAdj));
         }
-        mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+        if (infoSnapshot.isIntresetedToEventLog()) {
+            mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
+        }
         if (info.mPendingFullyDrawn != null) {
             info.mPendingFullyDrawn.run();
         }
@@ -1040,7 +1047,7 @@
     }
 
     // This gets called on another thread without holding the activity manager lock.
-    private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeNs,
+    private void logAppTransition(long transitionDeviceUptimeNs,
             int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
             int processState, int processOomAdj) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
@@ -1108,7 +1115,7 @@
                 isIncremental,
                 isLoading,
                 info.launchedActivityName.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+                TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
                 processState,
                 processOomAdj);
 
@@ -1132,10 +1139,6 @@
     }
 
     private void logAppDisplayed(TransitionInfoSnapshot info) {
-        if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
-            return;
-        }
-
         EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
                 info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
                 info.windowsDrawnDelayMs);
@@ -1181,8 +1184,7 @@
     }
 
     /** @see android.app.Activity#reportFullyDrawn */
-    TransitionInfoSnapshot logAppTransitionReportedDrawn(ActivityRecord r,
-            boolean restoredFromBundle) {
+    TransitionInfoSnapshot notifyFullyDrawn(ActivityRecord r, boolean restoredFromBundle) {
         final TransitionInfo info = mLastTransitionInfo.get(r);
         if (info == null) {
             return null;
@@ -1191,7 +1193,7 @@
             // There are still undrawn activities, postpone reporting fully drawn until all of its
             // windows are drawn. So that is closer to an usable state.
             info.mPendingFullyDrawn = () -> {
-                logAppTransitionReportedDrawn(r, restoredFromBundle);
+                notifyFullyDrawn(r, restoredFromBundle);
                 info.mPendingFullyDrawn = null;
             };
             return null;
@@ -1204,7 +1206,9 @@
                         currentTimestampNs - info.mLaunchingState.mStartUptimeNs);
         final TransitionInfoSnapshot infoSnapshot =
                 new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
-        mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+        if (infoSnapshot.isIntresetedToEventLog()) {
+            mLoggerHandler.post(() -> logAppFullyDrawn(infoSnapshot));
+        }
         mLastTransitionInfo.remove(r);
 
         if (!info.isInterestingToLoggerAndObserver()) {
@@ -1216,46 +1220,8 @@
         // fullfils (handling reportFullyDrawn() callbacks).
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 "ActivityManager:ReportingFullyDrawn " + info.mLastLaunchedActivity.packageName);
-
-        final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
-        builder.setPackageName(r.packageName);
-        builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
-        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
-        builder.setType(restoredFromBundle
-                ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
-                : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
-        builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
-                info.mProcessRunning ? 1 : 0);
-        mMetricsLogger.write(builder);
-        final PackageOptimizationInfo packageOptimizationInfo =
-                infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
-        // Incremental info
-        boolean isIncremental = false, isLoading = false;
-        final String codePath = info.mLastLaunchedActivity.info.applicationInfo.getCodePath();
-        if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
-            isIncremental = true;
-            isLoading = isIncrementalLoading(info.mLastLaunchedActivity.packageName,
-                            info.mLastLaunchedActivity.mUserId);
-        }
-        FrameworkStatsLog.write(
-                FrameworkStatsLog.APP_START_FULLY_DRAWN,
-                info.mLastLaunchedActivity.info.applicationInfo.uid,
-                info.mLastLaunchedActivity.packageName,
-                restoredFromBundle
-                        ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
-                        : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
-                info.mLastLaunchedActivity.info.name,
-                info.mProcessRunning,
-                startupTimeMs,
-                packageOptimizationInfo.getCompilationReason(),
-                packageOptimizationInfo.getCompilationFilter(),
-                info.mSourceType,
-                info.mSourceEventDelayMs,
-                isIncremental,
-                isLoading,
-                info.mLastLaunchedActivity.info.name.hashCode(),
-                TimeUnit.NANOSECONDS.toMillis(info.mLaunchingState.mStartRealtimeNs));
-
+        mLoggerHandler.post(() -> logAppFullyDrawnMetrics(infoSnapshot, restoredFromBundle,
+                info.mProcessRunning));
         // Ends the trace started at the beginning of this function. This is located here to allow
         // the trace slice to have a noticable duration.
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -1266,11 +1232,48 @@
         return infoSnapshot;
     }
 
-    private void logAppFullyDrawn(TransitionInfoSnapshot info) {
-        if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
-            return;
+    private void logAppFullyDrawnMetrics(TransitionInfoSnapshot info, boolean restoredFromBundle,
+            boolean processRunning) {
+        final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
+        builder.setPackageName(info.packageName);
+        builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivityName);
+        builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS,
+                (long) info.windowsFullyDrawnDelayMs);
+        builder.setType(restoredFromBundle
+                ? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
+                : TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE);
+        builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
+        mMetricsLogger.write(builder);
+        final PackageOptimizationInfo packageOptimizationInfo =
+                info.getPackageOptimizationInfo(getArtManagerInternal());
+        // Incremental info
+        boolean isIncremental = false, isLoading = false;
+        final String codePath = info.applicationInfo.getCodePath();
+        if (codePath != null && IncrementalManager.isIncrementalPath(codePath)) {
+            isIncremental = true;
+            isLoading = isIncrementalLoading(info.packageName, info.userId);
         }
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.APP_START_FULLY_DRAWN,
+                info.applicationInfo.uid,
+                info.packageName,
+                restoredFromBundle
+                        ? FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITH_BUNDLE
+                        : FrameworkStatsLog.APP_START_FULLY_DRAWN__TYPE__WITHOUT_BUNDLE,
+                info.launchedActivityName,
+                processRunning,
+                info.windowsFullyDrawnDelayMs,
+                packageOptimizationInfo.getCompilationReason(),
+                packageOptimizationInfo.getCompilationFilter(),
+                info.sourceType,
+                info.sourceEventDelayMs,
+                isIncremental,
+                isLoading,
+                info.launchedActivityName.hashCode(),
+                TimeUnit.NANOSECONDS.toMillis(info.timestampNs));
+    }
 
+    private void logAppFullyDrawn(TransitionInfoSnapshot info) {
         StringBuilder sb = mStringBuilder;
         sb.setLength(0);
         sb.append("Fully drawn ");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e5401f5..8f8b57f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3131,8 +3131,8 @@
         }
 
         // Check to see if PiP is supported for the display this container is on.
-        if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isWindowingModeSupported(
-                WINDOWING_MODE_PINNED)) {
+        if (mDisplayContent != null && !mDisplayContent.mDwpcHelper.isEnteringPipAllowed(
+                getUid())) {
             Slog.w(TAG, "Display " + mDisplayContent.getDisplayId()
                     + " doesn't support enter picture-in-picture mode. caller = " + caller);
             return false;
@@ -6497,15 +6497,6 @@
         }
     }
 
-    void reportFullyDrawnLocked(boolean restoredFromBundle) {
-        final TransitionInfoSnapshot info = mTaskSupervisor
-            .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
-        if (info != null) {
-            mTaskSupervisor.reportActivityLaunched(false /* timeout */, this,
-                    info.windowsFullyDrawnDelayMs, info.getLaunchState());
-        }
-    }
-
     void onFirstWindowDrawn(WindowState win) {
         firstWindowDrawn = true;
         // stop tracking
@@ -6908,11 +6899,8 @@
      *         {@link android.view.Display#INVALID_DISPLAY} if not attached.
      */
     int getDisplayId() {
-        final Task rootTask = getRootTask();
-        if (rootTask == null) {
-            return INVALID_DISPLAY;
-        }
-        return rootTask.getDisplayId();
+        return task != null && task.mDisplayContent != null
+                 ? task.mDisplayContent.mDisplayId : INVALID_DISPLAY;
     }
 
     final boolean isDestroyable() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3c457e1..2f70eda 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
 import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
 import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
 import static android.app.WaitResult.INVALID_DELAY;
@@ -2577,6 +2578,10 @@
                         // Apply options to prevent pendingOptions be taken when scheduling
                         // activity lifecycle transaction to make sure the override pending app
                         // transition will be applied immediately.
+                        if (activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+                            targetActivity.mPendingRemoteAnimation =
+                                    activityOptions.getRemoteAnimationAdapter();
+                        }
                         targetActivity.applyOptionsAnimation();
                         if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
                             targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b84b2d8..bedeabe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -369,6 +369,55 @@
     }
 
     @Override
+    ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+            ActivityRecord boundary) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return null;
+        }
+        return super.getActivity(callback, traverseTopToBottom, boundary);
+    }
+
+    @Override
+    Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return null;
+        }
+        return super.getTask(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllActivities(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllRootTasks(callback, traverseTopToBottom);
+    }
+
+    @Override
+    boolean forAllTasks(Predicate<Task> callback) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllTasks(callback);
+    }
+
+    @Override
+    boolean forAllLeafTasks(Predicate<Task> callback) {
+        if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+            return false;
+        }
+        return super.forAllLeafTasks(callback);
+    }
+
+    @Override
     void forAllDisplayAreas(Consumer<DisplayArea> callback) {
         super.forAllDisplayAreas(callback);
         callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8b34443..12efe0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -230,6 +230,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.view.inputmethod.ImeTracker;
 import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
 import android.window.ScreenCapture;
@@ -454,7 +455,7 @@
 
     /**
      * Compat metrics computed based on {@link #mDisplayMetrics}.
-     * @see #updateDisplayAndOrientation(int, Configuration)
+     * @see #updateDisplayAndOrientation(Configuration)
      */
     private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
 
@@ -5031,7 +5032,7 @@
      *   layer has been assigned since), to facilitate assigning the layer from the IME target, or
      *   fall back if there is no target.
      * - the container doesn't always participate in window traversal, according to
-     *   {@link #skipImeWindowsDuringTraversal()}
+     *   {@link #skipImeWindowsDuringTraversal(DisplayContent)}
      */
     private static class ImeContainer extends DisplayArea.Tokens {
         boolean mNeedsLayer = false;
@@ -6712,25 +6713,35 @@
                 mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
                         stateController.getControlsForDispatch(this));
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to deliver inset state change", e);
+                Slog.w(TAG, "Failed to deliver inset control state change", e);
             }
         }
 
         @Override
-        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             try {
-                mRemoteInsetsController.showInsets(types, fromIme);
+                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.get().onFailed(statsToken,
+                        ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
             }
         }
 
         @Override
-        public void hideInsets(@InsetsType int types, boolean fromIme) {
+        public void hideInsets(@InsetsType int types, boolean fromIme,
+                @Nullable ImeTracker.Token statsToken) {
             try {
-                mRemoteInsetsController.hideInsets(types, fromIme);
+                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 showInsets", e);
+                Slog.w(TAG, "Failed to deliver hideInsets", e);
+                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 2589148..1fef3c2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2071,7 +2071,8 @@
             // Don't show status bar when swiping on already visible navigation bar.
             // But restore the position of navigation bar if it has been moved by the control
             // target.
-            controlTarget.showInsets(Type.navigationBars(), false);
+            controlTarget.showInsets(Type.navigationBars(), false /* fromIme */,
+                    null /* statsToken */);
             return;
         }
 
@@ -2079,10 +2080,12 @@
             // Show transient bars if they are hidden; restore position if they are visible.
             mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
                     isGestureOnSystemBar);
-            controlTarget.showInsets(restorePositionTypes, false);
+            controlTarget.showInsets(restorePositionTypes, false /* fromIme */,
+                    null /* statsToken */);
         } else {
             // Restore visibilities and positions of system bars.
-            controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false);
+            controlTarget.showInsets(Type.statusBars() | Type.navigationBars(),
+                    false /* fromIme */, null /* statsToken */);
             // To further allow the pull-down-from-the-top gesture to pull down the notification
             // shade as a consistent motion, we reroute the touch events here from the currently
             // touched window to the status bar after making it visible.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 5d49042..6f821b5 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -162,6 +162,17 @@
         return mDisplayWindowPolicyController.canShowTasksInRecents();
     }
 
+    /**
+     * @see DisplayWindowPolicyController#isEnteringPipAllowed(int)
+     */
+    public final boolean isEnteringPipAllowed(int uid) {
+        if (mDisplayWindowPolicyController == null) {
+            return true;
+        }
+        return mDisplayWindowPolicyController.isEnteringPipAllowed(uid);
+    }
+
+
     void dump(String prefix, PrintWriter pw) {
         if (mDisplayWindowPolicyController != null) {
             pw.println();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 554791a..7fd093f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -33,9 +33,11 @@
 import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
+import android.view.InsetsSourceConsumer;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.WindowInsets;
+import android.view.inputmethod.ImeTracker;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -49,6 +51,9 @@
  */
 final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
 
+    /** The token tracking the current IME request or {@code null} otherwise. */
+    @Nullable
+    private ImeTracker.Token mImeRequesterStatsToken;
     private InsetsControlTarget mImeRequester;
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
@@ -162,14 +167,20 @@
     }
 
     /**
-     * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
-     * requests to show IME on {@param imeTarget}.
+     * Called from {@link WindowManagerInternal#showImePostLayout}
+     * when {@link android.inputmethodservice.InputMethodService} requests to show IME
+     * on {@param imeTarget}.
      *
-     * @param imeTarget imeTarget on which IME request is coming from.
+     * @param imeTarget imeTarget on which IME show request is coming from.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    void scheduleShowImePostLayout(InsetsControlTarget imeTarget) {
+    void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
+            @Nullable ImeTracker.Token statsToken) {
         boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
         mImeRequester = imeTarget;
+        // There was still a stats token, so that request presumably failed.
+        ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        mImeRequesterStatsToken = statsToken;
         if (targetChanged) {
             // target changed, check if new target can show IME.
             ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -183,15 +194,20 @@
         ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
                 ? mImeRequester : mImeRequester.getWindow().getName());
         mShowImeRunner = () -> {
+            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.get().onProgress(mImeRequesterStatsToken,
+                        ImeTracker.PHASE_WM_SHOW_IME_READY);
                 final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
 
                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
                         target.getWindow() != null ? target.getWindow().getName() : "");
                 setImeShowing(true);
-                target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                target.showInsets(WindowInsets.Type.ime(), true /* fromIme */,
+                        mImeRequesterStatsToken);
                 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 if (target != mImeRequester && mImeRequester != null) {
                     ProtoLog.w(WM_DEBUG_IME,
@@ -199,7 +215,12 @@
                             (mImeRequester.getWindow() != null
                                     ? mImeRequester.getWindow().getName() : ""));
                 }
+            } else {
+                ImeTracker.get().onFailed(mImeRequesterStatsToken,
+                        ImeTracker.PHASE_WM_SHOW_IME_READY);
             }
+            // Clear token here so we don't report an error in abortShowImePostLayout().
+            mImeRequesterStatsToken = null;
             abortShowImePostLayout();
         };
         mDisplayContent.mWmService.requestTraversal();
@@ -234,6 +255,8 @@
         mImeRequester = null;
         mIsImeLayoutDrawn = false;
         mShowImeRunner = null;
+        ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+        mImeRequesterStatsToken = null;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index d35b7c3..8ecbc17 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,9 +16,11 @@
 
 package com.android.server.wm;
 
+import android.annotation.Nullable;
 import android.inputmethodservice.InputMethodService;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 /**
  * Generalization of an object that can control insets state.
@@ -57,8 +59,10 @@
      *
      * @param types to specify which types of insets source window should be shown.
      * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    default void showInsets(@InsetsType int types, boolean fromIme) {
+    default void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     /**
@@ -66,8 +70,10 @@
      *
      * @param types to specify which types of insets source window should be hidden.
      * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    default void hideInsets(@InsetsType int types, boolean fromIme) {
+    default void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b9fa80c..f66fa0f 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -799,7 +799,7 @@
                         show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
                                 ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                                 : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                        null /* translator */);
+                        null /* translator */, null /* statsToken */);
                 SurfaceAnimationThread.getHandler().post(
                         () -> mListener.onReady(mAnimationControl, typesReady));
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c206a15..bab3a05 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -43,6 +43,7 @@
 import android.view.SurfaceControlViewHost;
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
@@ -729,16 +730,20 @@
      * Show IME on imeTargetWindow once IME has finished layout.
      *
      * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      */
-    public abstract void showImePostLayout(IBinder imeTargetWindowToken);
+    public abstract void showImePostLayout(IBinder imeTargetWindowToken,
+            @Nullable ImeTracker.Token statsToken);
 
     /**
      * Hide IME using imeTargetWindow when requested.
      *
      * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
      * @param displayId the id of the display the IME is on.
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    public abstract void hideIme(IBinder imeTargetWindowToken, int displayId);
+    public abstract void hideIme(IBinder imeTargetWindowToken, int displayId,
+            @Nullable ImeTracker.Token statsToken);
 
     /**
      * Tell window manager about a package that should be running with a restricted range of
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4080223..3419207 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -292,6 +292,7 @@
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
@@ -8014,7 +8015,8 @@
         }
 
         @Override
-        public void showImePostLayout(IBinder imeTargetWindowToken) {
+        public void showImePostLayout(IBinder imeTargetWindowToken,
+                @Nullable ImeTracker.Token statsToken) {
             synchronized (mGlobalLock) {
                 InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken);
                 if (imeTarget == null) {
@@ -8023,17 +8025,18 @@
                 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
                 imeTarget = controlTarget.getWindow();
-                // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
-                // is controlled by default display
+                // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget
+                // which is controlled by default display
                 final DisplayContent dc = imeTarget != null
                         ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
                 dc.getInsetsStateController().getImeSourceProvider()
-                        .scheduleShowImePostLayout(controlTarget);
+                        .scheduleShowImePostLayout(controlTarget, statsToken);
             }
         }
 
         @Override
-        public void hideIme(IBinder imeTargetWindowToken, int displayId) {
+        public void hideIme(IBinder imeTargetWindowToken, int displayId,
+                @Nullable ImeTracker.Token statsToken) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
             synchronized (mGlobalLock) {
                 WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
@@ -8049,10 +8052,15 @@
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
                 }
                 if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
+                    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 */);
+                    dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
+                            true /* fromIme */, statsToken);
+                } else {
+                    ImeTracker.get().onFailed(statsToken,
+                            ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
                 }
                 if (dc != null) {
                     dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2712cb7..5c5c703 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -249,6 +249,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 import android.window.OnBackInvokedCallbackInfo;
 
@@ -4018,7 +4019,7 @@
             mClient.insetsControlChanged(getCompatInsetsState(),
                     stateController.getControlsForDispatch(this));
         } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
+            Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
         }
     }
 
@@ -4028,20 +4029,30 @@
     }
 
     @Override
-    public void showInsets(@InsetsType int types, boolean fromIme) {
+    public void showInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         try {
-            mClient.showInsets(types, fromIme);
+            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.get().onFailed(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
         }
     }
 
     @Override
-    public void hideInsets(@InsetsType int types, boolean fromIme) {
+    public void hideInsets(@InsetsType int types, boolean fromIme,
+            @Nullable ImeTracker.Token statsToken) {
         try {
-            mClient.hideInsets(types, fromIme);
+            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 showInsets", e);
+            Slog.w(TAG, "Failed to deliver hideInsets", e);
+            ImeTracker.get().onFailed(statsToken,
+                    ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
         }
     }
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3197124..2030347 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -260,10 +260,9 @@
 
 // --- NativeInputManager ---
 
-class NativeInputManager : public virtual RefBase,
-    public virtual InputReaderPolicyInterface,
-    public virtual InputDispatcherPolicyInterface,
-    public virtual PointerControllerPolicyInterface {
+class NativeInputManager : public virtual InputReaderPolicyInterface,
+                           public virtual InputDispatcherPolicyInterface,
+                           public virtual PointerControllerPolicyInterface {
 protected:
     virtual ~NativeInputManager();
 
@@ -1520,8 +1519,14 @@
         return 0;
     }
 
-    NativeInputManager* im = new NativeInputManager(serviceObj, messageQueue->getLooper());
-    im->incStrong(0);
+    static std::once_flag nativeInitialize;
+    NativeInputManager* im = nullptr;
+    std::call_once(nativeInitialize, [&]() {
+        // Create the NativeInputManager, which should not be destroyed or deallocated for the
+        // lifetime of the process.
+        im = new NativeInputManager(serviceObj, messageQueue->getLooper());
+    });
+    LOG_ALWAYS_FATAL_IF(im == nullptr, "NativeInputManager was already initialized.");
     return reinterpret_cast<jlong>(im);
 }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 9fa23c2..e1de05c 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -482,6 +482,17 @@
     agnssRilIface->setSetId(type, setid_string);
 }
 
+static void android_location_gnss_hal_GnssNative_inject_ni_supl_message_data(JNIEnv* env, jclass,
+                                                                             jbyteArray data,
+                                                                             jint length,
+                                                                             jint slotIndex) {
+    if (agnssRilIface == nullptr) {
+        ALOGE("%s: IAGnssRil interface not available.", __func__);
+        return;
+    }
+    agnssRilIface->injectNiSuplMessageData(data, length, slotIndex);
+}
+
 static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass,
                                                            jbyteArray nmeaArray, jint buffer_size) {
     return gnssHal->readNmea(nmeaArray, buffer_size);
@@ -974,6 +985,8 @@
                  android_location_gnss_hal_GnssNative_agps_set_reference_location_cellid)},
         {"native_set_agps_server", "(ILjava/lang/String;I)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
+        {"native_inject_ni_supl_message_data", "([BII)V",
+         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
         {"native_send_ni_response", "(II)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
         {"native_get_internal_state", "()Ljava/lang/String;",
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index 34e4976..c7a1af7 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -84,8 +84,19 @@
     networkAttributes.capabilities = static_cast<int32_t>(capabilities),
     networkAttributes.apn = jniApn.c_str();
 
-    auto result = mIAGnssRil->updateNetworkState(networkAttributes);
-    return checkAidlStatus(result, "IAGnssRilAidl updateNetworkState() failed.");
+    auto status = mIAGnssRil->updateNetworkState(networkAttributes);
+    return checkAidlStatus(status, "IAGnssRilAidl updateNetworkState() failed.");
+}
+
+jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+    JNIEnv* env = getJniEnv();
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
+    auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
+                                                                           (const uint8_t*)bytes +
+                                                                                   length),
+                                                      static_cast<int>(slotIndex));
+    env->ReleasePrimitiveArrayCritical(msgData, bytes, JNI_ABORT);
+    return checkAidlStatus(status, "IAGnssRil injectNiSuplMessageData() failed.");
 }
 
 // Implementation of AGnssRil_V1_0
@@ -151,6 +162,11 @@
     return checkHidlReturn(result, "IAGnssRil_V1_0 updateNetworkState() failed.");
 }
 
+jboolean AGnssRil_V1_0::injectNiSuplMessageData(const jbyteArray&, jint, jint) {
+    ALOGI("IAGnssRil_V1_0 interface does not support injectNiSuplMessageData.");
+    return JNI_FALSE;
+}
+
 // Implementation of AGnssRil_V2_0
 
 AGnssRil_V2_0::AGnssRil_V2_0(const sp<IAGnssRil_V2_0>& iAGnssRil)
diff --git a/services/core/jni/gnss/AGnssRil.h b/services/core/jni/gnss/AGnssRil.h
index ce14a77d..b7e0282 100644
--- a/services/core/jni/gnss/AGnssRil.h
+++ b/services/core/jni/gnss/AGnssRil.h
@@ -43,6 +43,8 @@
     virtual jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming,
                                         jboolean available, const jstring& apn, jlong networkHandle,
                                         jshort capabilities) = 0;
+    virtual jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                             jint slotIndex) = 0;
 };
 
 class AGnssRil : public AGnssRilInterface {
@@ -55,6 +57,8 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray& msgData, jint length,
+                                     jint slotIndex) override;
 
 private:
     const sp<android::hardware::gnss::IAGnssRil> mIAGnssRil;
@@ -70,6 +74,7 @@
     jboolean updateNetworkState(jboolean connected, jint type, jboolean roaming, jboolean available,
                                 const jstring& apn, jlong networkHandle,
                                 jshort capabilities) override;
+    jboolean injectNiSuplMessageData(const jbyteArray&, jint, jint) override;
 
 private:
     const sp<android::hardware::gnss::V1_0::IAGnssRil> mAGnssRil_V1_0;
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
new file mode 100644
index 0000000..e07bc77
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.credentials.CreateCredentialRequest;
+import android.credentials.CredentialManager;
+import android.credentials.ICreateCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * This class listens to the responses from providers, and the UX app, and updates the
+ * provider(s) state maintained in {@link ProviderCreateSession}.
+ */
+public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
+        ICreateCredentialCallback> {
+    private static final String TAG = "CreateRequestSession";
+
+    CreateRequestSession(@NonNull Context context, int userId,
+            CreateCredentialRequest request,
+            ICreateCredentialCallback callback,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_CREATE, callingPackage);
+    }
+
+    /**
+     * Creates a new provider session, and adds it to list of providers that are contributing to
+     * this request session.
+     *
+     * @return the provider session that was started
+     */
+    @Override
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderCreateSession providerCreateSession = ProviderCreateSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                this, remoteCredentialService);
+        if (providerCreateSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerCreateSession.getComponentName().flattenToString(),
+                    providerCreateSession);
+        }
+        return providerCreateSession;
+    }
+
+    @Override
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newCreateRequestInfo(
+                        mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
+                providerDataList));
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 321f022..374da1c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialRequest;
 import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
@@ -33,6 +34,7 @@
 import android.os.ICancellationSignal;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.credentials.GetCredentialsRequest;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -43,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Entry point service for credential management.
@@ -91,17 +94,15 @@
             return new ArrayList<>();
         }
         List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
-        for (int i = 0; i < serviceNames.length; i++) {
-            Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
-            if (TextUtils.isEmpty(serviceNames[i])) {
+        for (String serviceName : serviceNames) {
+            Log.i(TAG, "in newServiceListLocked, service: " + serviceName);
+            if (TextUtils.isEmpty(serviceName)) {
                 continue;
             }
             try {
                 serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
-                        serviceNames[i]));
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
-            } catch (SecurityException e) {
+                        serviceName));
+            } catch (PackageManager.NameNotFoundException | SecurityException e) {
                 Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
             }
         }
@@ -115,15 +116,31 @@
             synchronized (mLock) {
                 final List<CredentialManagerServiceImpl> services =
                         getServiceListForUserLocked(userId);
-                services.forEach(s -> {
+                for (CredentialManagerServiceImpl s : services) {
                     c.accept(s);
-                });
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    private List<ProviderSession> initiateProviderSessions(RequestSession session,
+            List<String> requestOptions) {
+        List<ProviderSession> providerSessions = new ArrayList<>();
+        // Invoke all services of a user to initiate a provider session
+        runForUser((service) -> {
+            if (service.isServiceCapable(requestOptions)) {
+                ProviderSession providerSession = service
+                        .initiateProviderSessionForRequest(session);
+                if (providerSession != null) {
+                    providerSessions.add(providerSession);
+                }
+            }
+        });
+        return providerSessions;
+    }
+
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
         @Override
         public ICancellationSignal executeGetCredential(
@@ -137,11 +154,22 @@
             // New request session, scoped for this request only.
             final GetRequestSession session = new GetRequestSession(getContext(),
                     UserHandle.getCallingUserId(),
-                    callback);
+                    callback,
+                    request,
+                    callingPackage);
 
-            // Invoke all services of a user
-            runForUser((service) -> {
-                service.getCredential(request, session, callingPackage);
+            // Initiate all provider sessions
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, request.getGetCredentialOptions()
+                            .stream().map(GetCredentialOption::getType)
+                            .collect(Collectors.toList()));
+            // TODO : Return error when no providers available
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(providerGetSession -> {
+                providerGetSession.getRemoteCredentialService().onGetCredentials(
+                        (GetCredentialsRequest) providerGetSession.getProviderRequest(),
+                        /*callback=*/providerGetSession);
             });
             return cancelTransport;
         }
@@ -151,9 +179,29 @@
                 CreateCredentialRequest request,
                 ICreateCredentialCallback callback,
                 String callingPackage) {
-            // TODO: implement.
-            Log.i(TAG, "executeCreateCredential");
+            Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+            // TODO : Implement cancellation
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // New request session, scoped for this request only.
+            final CreateRequestSession session = new CreateRequestSession(getContext(),
+                    UserHandle.getCallingUserId(),
+                    request,
+                    callback,
+                    callingPackage);
+
+            // Initiate all provider sessions
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, List.of(request.getType()));
+            // TODO : Return error when no providers available
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(providerCreateSession -> {
+                providerCreateSession.getRemoteCredentialService().onCreateCredential(
+                        (android.service.credentials.CreateCredentialRequest)
+                                providerCreateSession.getProviderRequest(),
+                        /*callback=*/providerCreateSession);
+            });
             return cancelTransport;
         }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index cc03f9b..0c32304 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,13 +21,13 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.credentials.GetCredentialRequest;
 import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.GetCredentialsRequest;
 import android.util.Slog;
 
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import java.util.List;
+
 
 /**
  * Per-user, per remote service implementation of {@link CredentialManagerService}
@@ -61,50 +61,38 @@
         return mInfo.getServiceInfo();
     }
 
-    public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
-            String callingPackage) {
-        Slog.i(TAG, "in getCredential in CredManServiceImpl");
+    /**
+     * Starts a provider session and associates it with the given request session. */
+    @Nullable
+    public ProviderSession initiateProviderSessionForRequest(
+            RequestSession requestSession) {
+        Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl");
         if (mInfo == null) {
-            Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
-            return;
+            Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+                    + "but mInfo is null. This shouldn't happen");
+            return null;
         }
-
-        // TODO : Determine if remoteService instance can be reused across requests
         final RemoteCredentialService remoteService = new RemoteCredentialService(
                 getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
-        ProviderGetSession providerSession = new ProviderGetSession(mInfo,
-                requestSession, mUserId, remoteService);
-        // Set the provider info to the session when the request is initiated. This happens here
-        // because there is one serviceImpl per remote provider, and so we can only retrieve
-        // the provider information in the scope of this instance, whereas the session is for the
-        // entire request.
-        requestSession.addProviderSession(providerSession);
-        GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
-        if (filteredRequest != null) {
-            remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
-                    providerSession);
-        }
+        ProviderSession providerSession =
+                requestSession.initiateProviderSession(mInfo, remoteService);
+        return providerSession;
     }
 
-    @Nullable
-    private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
-            String callingPackage) {
-        GetCredentialsRequest.Builder builder =
-                new GetCredentialsRequest.Builder(callingPackage);
-        request.getGetCredentialOptions().forEach( option -> {
-            if (mInfo.hasCapability(option.getType())) {
-                Slog.i(TAG, "Provider can handle: " + option.getType());
-                builder.addGetCredentialOption(option);
-            } else {
-                Slog.i(TAG, "Skipping request as provider cannot handle it");
-            }
-        });
-
-        try {
-            return builder.build();
-        } catch (IllegalArgumentException | NullPointerException e) {
-            Slog.i(TAG, "issue with request build: " + e.getMessage());
+    /** Return true if at least one capability found. */
+    boolean isServiceCapable(List<String> requestedOptions) {
+        if (mInfo == null) {
+            Slog.i(TAG, "in isServiceCapable, mInfo is null");
+            return false;
         }
-        return null;
+        for (String capability : requestedOptions) {
+            if (mInfo.hasCapability(capability)) {
+                Slog.i(TAG, "Provider can handle: " + capability);
+                return true;
+            } else {
+                Slog.i(TAG, "Provider cannot handle: " + capability);
+            }
+        }
+        return false;
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index dcf094f..e889594 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -37,6 +37,7 @@
     @NonNull
     private final CredentialManagerUiCallback mCallbacks;
     @NonNull private final Context mContext;
+    // TODO : Use for starting the activity for this user
     private final int mUserId;
     @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
             new Handler(Looper.getMainLooper())) {
@@ -56,7 +57,7 @@
                 Slog.i(TAG, "No selection found in UI result");
             }
         } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
-            mCallbacks.onUiCancelation();
+            mCallbacks.onUiCancellation();
         }
     }
 
@@ -67,7 +68,7 @@
         /** Called when the user makes a selection. */
         void onUiSelection(UserSelectionDialogResult selection);
         /** Called when the user cancels the UI. */
-        void onUiCancelation();
+        void onUiCancellation();
     }
     public CredentialManagerUi(Context context, int userId,
             CredentialManagerUiCallback callbacks) {
@@ -83,9 +84,8 @@
      */
     public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
         Log.i(TAG, "In show");
-        Intent intent = IntentFactory.newIntent(
-                requestInfo, providerDataList,
-                new ArrayList<>(), mResultReceiver);
+        Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, new ArrayList<>(),
+                mResultReceiver);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(intent);
     }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 80f0fec..8238632 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,9 +16,10 @@
 
 package com.android.server.credentials;
 
-import android.content.ComponentName;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.credentials.Credential;
+import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.IGetCredentialCallback;
 import android.credentials.ui.ProviderData;
@@ -26,62 +27,52 @@
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.RemoteException;
 import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfo;
 import android.util.Log;
-import android.util.Slog;
 
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Central session for a single getCredentials request. This class listens to the
  * responses from providers, and the UX app, and updates the provider(S) state.
  */
-public final class GetRequestSession extends RequestSession {
+public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+        IGetCredentialCallback> {
     private static final String TAG = "GetRequestSession";
 
-    private final IGetCredentialCallback mClientCallback;
-    private final Map<String, ProviderGetSession> mProviders;
-
     public GetRequestSession(Context context, int userId,
-            IGetCredentialCallback callback) {
-        super(context, userId, RequestInfo.TYPE_GET);
-        mClientCallback = callback;
-        mProviders = new HashMap<>();
+            IGetCredentialCallback callback, GetCredentialRequest request,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_GET, callingPackage);
     }
 
     /**
-     * Adds a new provider to the list of providers that are contributing to this session.
+     * Creates a new provider session, and adds it list of providers that are contributing to
+     * this session.
+     * @return the provider session created within this request session, for the given provider
+     * info.
      */
-    public void addProviderSession(ProviderGetSession providerSession) {
-        mProviders.put(providerSession.getComponentName().flattenToString(),
-                providerSession);
-    }
-
     @Override
-    public void onProviderStatusChanged(ProviderSession.Status status,
-            ComponentName componentName) {
-        Log.i(TAG, "in onStatusChanged");
-        if (ProviderSession.isTerminatingStatus(status)) {
-            Log.i(TAG, "in onStatusChanged terminating status");
-
-            ProviderGetSession session = mProviders.remove(componentName.flattenToString());
-            if (session != null) {
-                Slog.i(TAG, "Provider session removed.");
-            } else {
-                Slog.i(TAG, "Provider session null, did not exist.");
-            }
-        } else if (ProviderSession.isCompletionStatus(status)) {
-            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
-            onProviderResponseComplete();
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderGetSession providerGetSession = ProviderGetSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                        this, remoteCredentialService);
+        if (providerGetSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerGetSession.getComponentName().flattenToString(),
+                    providerGetSession);
         }
+        return providerGetSession;
     }
 
+    // TODO: Override for this method not needed once get selection logic is
+    //  moved to ProviderGetSession
     @Override
     public void onUiSelection(UserSelectionDialogResult selection) {
         String providerId = selection.getProviderId();
-        ProviderGetSession providerSession = mProviders.get(providerId);
+        ProviderGetSession providerSession = (ProviderGetSession) mProviders.get(providerId);
         if (providerSession != null) {
             CredentialEntry credentialEntry = providerSession.getCredentialEntry(
                     selection.getEntrySubkey());
@@ -89,57 +80,17 @@
                 respondToClientAndFinish(credentialEntry.getCredential());
             }
             // TODO : Handle action chips and authentication selection
-            return;
         }
         // TODO : finish session and respond to client if provider not found
     }
 
     @Override
-    public void onUiCancelation() {
-        // User canceled the activity
-        // TODO : Send error code to client
-        finishSession();
-    }
-
-    private void onProviderResponseComplete() {
-        Log.i(TAG, "in onProviderResponseComplete");
-        if (isResponseCompleteAcrossProviders()) {
-            Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
-            getProviderDataAndInitiateUi();
-        }
-    }
-
-    private void getProviderDataAndInitiateUi() {
-        ArrayList<ProviderData> providerDataList = new ArrayList<>();
-        for (ProviderGetSession session : mProviders.values()) {
-            Log.i(TAG, "preparing data for : " + session.getComponentName());
-            providerDataList.add(session.prepareUiData());
-        }
-        if (!providerDataList.isEmpty()) {
-            Log.i(TAG, "provider list not empty about to initiate ui");
-            initiateUi(providerDataList);
-        }
-    }
-
-    private void initiateUi(ArrayList<ProviderData> providerDataList) {
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
         mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
                 mRequestId, null, mIsFirstUiTurn, ""),
                 providerDataList));
     }
 
-    /**
-     * Iterates over all provider sessions and returns true if all have responded.
-     */
-    private boolean isResponseCompleteAcrossProviders() {
-        AtomicBoolean isRequestComplete = new AtomicBoolean(true);
-        mProviders.forEach( (packageName, session) -> {
-            if (session.getStatus() != ProviderSession.Status.COMPLETE) {
-                isRequestComplete.set(false);
-            }
-        });
-        return isRequestComplete.get();
-    }
-
     private void respondToClientAndFinish(Credential credential) {
         try {
             mClientCallback.onResponse(new GetCredentialResponse(credential));
@@ -148,17 +99,4 @@
         }
         finishSession();
     }
-
-    private void finishSession() {
-        clearProviderSessions();
-    }
-
-    private void clearProviderSessions() {
-        for (ProviderGetSession session : mProviders.values()) {
-            // TODO : Evaluate if we should unbind remote services here or wait for them
-            // to automatically unbind when idle. Re-binding frequently also has a cost.
-            //session.destroy();
-        }
-        mProviders.clear();
-    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
new file mode 100644
index 0000000..49c416f
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -0,0 +1,231 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.ui.CreateCredentialProviderData;
+import android.credentials.ui.Entry;
+import android.os.Bundle;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.SaveEntry;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ * Will likely split this into remote response state and UI state.
+ */
+public final class ProviderCreateSession extends ProviderSession<
+        CreateCredentialRequest, CreateCredentialResponse> {
+    private static final String TAG = "ProviderCreateSession";
+
+    // Key to be used as an entry key for a save entry
+    private static final String SAVE_ENTRY_KEY = "save_entry_key";
+
+    @NonNull
+    private final Map<String, SaveEntry> mUiSaveEntries = new HashMap<>();
+    /** The complete request to be used in the second round. */
+    private final CreateCredentialRequest mCompleteRequest;
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderCreateSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            CreateRequestSession createRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        CreateCredentialRequest providerRequest =
+                createProviderRequest(providerInfo.getCapabilities(),
+                        createRequestSession.mClientRequest,
+                        createRequestSession.mClientCallingPackage);
+        if (providerRequest != null) {
+            return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
+                    remoteCredentialService, providerRequest);
+        }
+        Log.i(TAG, "Unable to create provider session");
+        return null;
+    }
+
+    @Nullable
+    private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+            android.credentials.CreateCredentialRequest clientRequest,
+            String clientCallingPackage) {
+        String capability = clientRequest.getType();
+        if (providerCapabilities.contains(capability)) {
+            return new CreateCredentialRequest(clientCallingPackage, capability,
+                    clientRequest.getData());
+        }
+        Log.i(TAG, "Unable to create provider request - capabilities do not match");
+        return null;
+    }
+
+    private static CreateCredentialRequest getFirstRoundRequest(CreateCredentialRequest request) {
+        // TODO: Replace with first round bundle from request when ready
+        return new CreateCredentialRequest(
+                request.getCallingPackage(),
+                request.getType(),
+                new Bundle());
+    }
+
+    private ProviderCreateSession(
+            @NonNull Context context,
+            @NonNull CredentialProviderInfo info,
+            @NonNull ProviderInternalCallback callbacks,
+            @UserIdInt int userId,
+            @NonNull RemoteCredentialService remoteCredentialService,
+            @NonNull CreateCredentialRequest request) {
+        super(context, info, getFirstRoundRequest(request), callbacks, userId,
+                remoteCredentialService);
+        // TODO : Replace with proper splitting of request
+        mCompleteRequest = request;
+        setStatus(Status.PENDING);
+    }
+
+    /** Returns the save entry maintained in state by this provider session. */
+    public SaveEntry getUiSaveEntry(String entryId) {
+        return mUiSaveEntries.get(entryId);
+    }
+
+    @Override
+    public void onProviderResponseSuccess(
+            @Nullable CreateCredentialResponse response) {
+        Log.i(TAG, "in onProviderResponseSuccess");
+        onUpdateResponse(response);
+    }
+
+    /** Called when the provider response resulted in a failure. */
+    @Override
+    public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
+        updateStatusAndInvokeCallback(toStatus(errorCode));
+    }
+
+    /** Called when provider service dies. */
+    @Override
+    public void onProviderServiceDied(RemoteCredentialService service) {
+        if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+            updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+        } else {
+            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+                    + "this should not happen");
+        }
+    }
+
+    private void onUpdateResponse(CreateCredentialResponse response) {
+        Log.i(TAG, "updateResponse with save entries");
+        mProviderResponse = response;
+        updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+    }
+
+    @Override
+    @Nullable protected CreateCredentialProviderData prepareUiData()
+            throws IllegalArgumentException {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+            return null;
+        }
+        final CreateCredentialResponse response = getProviderResponse();
+        if (response == null) {
+            Log.i(TAG, "In prepareUiData response null");
+            throw new IllegalStateException("Response must be in completion mode");
+        }
+        if (response.getSaveEntries() != null) {
+            Log.i(TAG, "In prepareUiData save entries not null");
+            return prepareUiProviderData(
+                    prepareUiSaveEntries(response.getSaveEntries()),
+                    null,
+                    /*isDefaultProvider=*/false);
+        }
+        return null;
+    }
+
+    @Override
+    public void onProviderIntentResult(Bundle resultData) {
+        Credential credential = resultData.getParcelable(
+                CredentialProviderService.EXTRA_SAVE_CREDENTIAL,
+                Credential.class);
+        if (credential == null) {
+            Log.i(TAG, "Credential returned from intent is null");
+            return;
+        }
+        updateFinalCredentialResponse(credential);
+    }
+
+    @Override
+    public void onUiEntrySelected(String entryType, String entryKey) {
+        if (entryType.equals(SAVE_ENTRY_KEY)) {
+            SaveEntry saveEntry = mUiSaveEntries.get(entryKey);
+            if (saveEntry == null) {
+                Log.i(TAG, "Save entry not found");
+                return;
+            }
+            // TODO: Uncomment when pending intent works
+            // onSaveEntrySelected(saveEntry);
+        }
+    }
+
+    @Override
+    public void onProviderIntentCancelled() {
+        //TODO (Implement)
+    }
+
+    private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+        Log.i(TAG, "in populateUiSaveEntries");
+        List<Entry> uiSaveEntries = new ArrayList<>();
+
+        // Populate the save entries
+        for (SaveEntry saveEntry : saveEntries) {
+            String entryId = generateEntryId();
+            mUiSaveEntries.put(entryId, saveEntry);
+            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+            uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice()));
+        }
+        return uiSaveEntries;
+    }
+
+    private void updateFinalCredentialResponse(@NonNull Credential credential) {
+        mFinalCredentialResponse = credential;
+        updateStatusAndInvokeCallback(Status.CREDENTIAL_RECEIVED_FROM_INTENT);
+    }
+
+    private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
+            Entry remoteEntry, boolean isDefaultProvider) {
+        return new CreateCredentialProviderData.Builder(
+                mComponentName.flattenToString())
+                .setSaveEntries(saveEntries)
+                .setIsDefaultProvider(isDefaultProvider)
+                .build();
+    }
+
+    private void onSaveEntrySelected(SaveEntry saveEntry) {
+        mProviderIntentController.setupAndInvokePendingIntent(saveEntry.getPendingIntent(),
+                mProviderRequest);
+        setStatus(Status.PENDING_INTENT_INVOKED);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ff2107a..362d981 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -18,13 +18,16 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.slice.Slice;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.GetCredentialOption;
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
+import android.os.Bundle;
 import android.service.credentials.Action;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderInfo;
-import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.GetCredentialsRequest;
 import android.service.credentials.GetCredentialsResponse;
 import android.util.Log;
 import android.util.Slog;
@@ -38,75 +41,95 @@
 /**
  * Central provider session that listens for provider callbacks, and maintains provider state.
  * Will likely split this into remote response state and UI state.
+ *
+ * @hide
  */
-public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
-        implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
+        GetCredentialsResponse>
+        implements
+        RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
     private static final String TAG = "ProviderGetSession";
 
     // Key to be used as an entry key for a credential entry
     private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
 
-    private GetCredentialsResponse mResponse;
-
     @NonNull
-    private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
-
+    private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
     @NonNull
-    private final Map<String, Action> mUiActions = new HashMap<>();
+    private final Map<String, Action> mUiActionsEntries = new HashMap<>();
+    private Action mAuthenticationAction = null;
 
-    public ProviderGetSession(CredentialProviderInfo info,
-            ProviderInternalCallback callbacks,
-            int userId, RemoteCredentialService remoteCredentialService) {
-        super(info, callbacks, userId, remoteCredentialService);
-        setStatus(Status.PENDING);
-    }
-
-    /** Updates the response being maintained in state by this provider session. */
-    @Override
-    public void updateResponse(GetCredentialsResponse response) {
-        if (response.getAuthenticationAction() != null) {
-            // TODO : Implement authentication logic
-        } else if (response.getCredentialsDisplayContent() != null) {
-            Log.i(TAG , "updateResponse with credentialEntries");
-            mResponse = response;
-            updateStatusAndInvokeCallback(Status.COMPLETE);
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderGetSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            GetRequestSession getRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        GetCredentialsRequest providerRequest =
+                createProviderRequest(providerInfo.getCapabilities(),
+                        getRequestSession.mClientRequest,
+                        getRequestSession.mClientCallingPackage);
+        if (providerRequest != null) {
+            return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
+                    remoteCredentialService, providerRequest);
         }
+        Log.i(TAG, "Unable to create provider session");
+        return null;
     }
 
-    /** Returns the response being maintained in this provider session. */
-    @Override
     @Nullable
-    public GetCredentialsResponse getResponse() {
-        return  mResponse;
+    private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
+            android.credentials.GetCredentialRequest clientRequest,
+            String clientCallingPackage) {
+        List<GetCredentialOption> filteredOptions = new ArrayList<>();
+        for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+            if (providerCapabilities.contains(option.getType())) {
+                Log.i(TAG, "In createProviderRequest - capability found : " + option.getType());
+                filteredOptions.add(option);
+            } else {
+                Log.i(TAG, "In createProviderRequest - capability not "
+                        + "found : " + option.getType());
+            }
+        }
+        if (!filteredOptions.isEmpty()) {
+            return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
+                    filteredOptions).build();
+        }
+        Log.i(TAG, "In createProviderRequest - returning null");
+        return null;
+    }
+
+    public ProviderGetSession(Context context,
+            CredentialProviderInfo info,
+            ProviderInternalCallback callbacks,
+            int userId, RemoteCredentialService remoteCredentialService,
+            GetCredentialsRequest request) {
+        super(context, info, request, callbacks, userId, remoteCredentialService);
+        setStatus(Status.PENDING);
     }
 
     /** Returns the credential entry maintained in state by this provider session. */
     @Nullable
     public CredentialEntry getCredentialEntry(@NonNull String entryId) {
-        return mUiCredentials.get(entryId);
-    }
-
-    /** Returns the action entry maintained in state by this provider session. */
-    @Nullable
-    public Action getAction(@NonNull String entryId) {
-        return mUiActions.get(entryId);
+        return mUiCredentialEntries.get(entryId);
     }
 
     /** Called when the provider response has been updated by an external source. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
         Log.i(TAG, "in onProviderResponseSuccess");
-        updateResponse(response);
+        onUpdateResponse(response);
     }
 
     /** Called when the provider response resulted in a failure. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
         updateStatusAndInvokeCallback(toStatus(errorCode));
     }
 
     /** Called when provider service dies. */
-    @Override
+    @Override // Callback from the remote provider
     public void onProviderServiceDied(RemoteCredentialService service) {
         if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
             updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
@@ -116,77 +139,106 @@
         }
     }
 
-    @Override
-    protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
-        Log.i(TAG, "In prepareUiData");
-        if (!ProviderSession.isCompletionStatus(getStatus())) {
-            Log.i(TAG, "In prepareUiData not complete");
+    @Override // Callback from the provider intent controller class
+    public void onProviderIntentResult(Bundle resultData) {
+        // TODO : Implement
+    }
 
-            throw new IllegalStateException("Status must be in completion mode");
+    @Override
+    public void onProviderIntentCancelled() {
+        // TODO : Implement
+    }
+
+    @Override // Selection call from the request provider
+    protected void onUiEntrySelected(String entryType, String entryId) {
+        // TODO: Implement
+    }
+
+    @Override // Call from request session to data to be shown on the UI
+    @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isUiInvokingStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
+                    + mComponentName.flattenToString());
+            return null;
         }
-        GetCredentialsResponse response = getResponse();
+        GetCredentialsResponse response = getProviderResponse();
         if (response == null) {
             Log.i(TAG, "In prepareUiData response null");
-
             throw new IllegalStateException("Response must be in completion mode");
         }
         if (response.getAuthenticationAction() != null) {
-            Log.i(TAG, "In prepareUiData auth not null");
-
-            return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
+            Log.i(TAG, "In prepareUiData - top level authentication mode");
+            return prepareUiProviderData(null, null,
+                    prepareUiAuthenticationActionEntry(response.getAuthenticationAction()),
+                    /*remoteEntry=*/null);
         }
         if (response.getCredentialsDisplayContent() != null){
-            Log.i(TAG, "In prepareUiData credentials not null");
-
-            return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+            Log.i(TAG, "In prepareUiData displayContent not null");
+            return prepareUiProviderData(populateUiActionEntries(
+                            response.getCredentialsDisplayContent().getActions()),
+                    prepareUiCredentialEntries(response.getCredentialsDisplayContent()
+                            .getCredentialEntries()),
+                    /*authenticationActionEntry=*/null, /*remoteEntry=*/null);
         }
         return null;
     }
 
-    /**
-     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
-     */
-    @Nullable
-    private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
-            CredentialsDisplayContent content) {
-        Log.i(TAG, "in prepareUiProviderData");
-        List<Entry> credentialEntries = new ArrayList<>();
-        List<Entry> actionChips = new ArrayList<>();
-        Entry authenticationEntry = null;
+    private Entry prepareUiAuthenticationActionEntry(@NonNull Action authenticationAction) {
+        String entryId = generateEntryId();
+        mUiActionsEntries.put(entryId, authenticationAction);
+        return new Entry(ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice());
+    }
+
+    private List<Entry> prepareUiCredentialEntries(@NonNull
+            List<CredentialEntry> credentialEntries) {
+        Log.i(TAG, "in prepareUiProviderDataWithCredentials");
+        List<Entry> credentialUiEntries = new ArrayList<>();
 
         // Populate the credential entries
-        for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
-            String entryId = UUID.randomUUID().toString();
-            mUiCredentials.put(entryId, credentialEntry);
+        for (CredentialEntry credentialEntry : credentialEntries) {
+            String entryId = generateEntryId();
+            mUiCredentialEntries.put(entryId, credentialEntry);
             Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
-            Slice slice = credentialEntry.getSlice();
-            // TODO : Remove conversion of string to int after change in Entry class
-            credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+            credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice()));
         }
-        // populate the action chip
-        for (Action action : content.getActions()) {
-            String entryId = UUID.randomUUID().toString();
-            mUiActions.put(entryId, action);
-            // TODO : Remove conversion of string to int after change in Entry class
-            actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
-                    action.getSlice()));
-        }
+        return credentialUiEntries;
+    }
 
-        return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
+    private List<Entry> populateUiActionEntries(@Nullable List<Action> actions) {
+        List<Entry> actionEntries = new ArrayList<>();
+        for (Action action : actions) {
+            String entryId = UUID.randomUUID().toString();
+            mUiActionsEntries.put(entryId, action);
+            // TODO : Remove conversion of string to int after change in Entry class
+            actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
+        }
+        return actionEntries;
+    }
+
+    private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
+            List<Entry> credentialEntries, Entry authenticationActionEntry,
+            Entry remoteEntry) {
+        return new GetCredentialProviderData.Builder(
+                mComponentName.flattenToString()).setActionChips(actionEntries)
                 .setCredentialEntries(credentialEntries)
-                .setActionChips(actionChips)
-                .setAuthenticationEntry(authenticationEntry)
+                .setAuthenticationEntry(authenticationActionEntry)
                 .build();
     }
 
-    /**
-     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
-     */
-    @Nullable
-    private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
-            Action authenticationEntry) {
-        // TODO : Implement authentication flow
-        return null;
+    /** Updates the response being maintained in state by this provider session. */
+    private void onUpdateResponse(GetCredentialsResponse response) {
+        mProviderResponse = response;
+        if (response.getAuthenticationAction() != null) {
+            Log.i(TAG , "updateResponse with authentication entry");
+            // TODO validate authentication action
+            mAuthenticationAction = response.getAuthenticationAction();
+            updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
+        } else if (response.getCredentialsDisplayContent() != null) {
+            Log.i(TAG , "updateResponse with credentialEntries");
+            // TODO validate response
+            updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderIntentController.java b/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
new file mode 100644
index 0000000..0f2e8ec
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
@@ -0,0 +1,117 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CredentialProviderService;
+import android.util.Log;
+
+/**
+ * Class that invokes providers' pending intents and listens to the responses.
+ */
+@SuppressLint("LongLogTag")
+public class ProviderIntentController {
+    private static final String TAG = "ProviderIntentController";
+    /**
+     * Interface to be implemented by any class that wishes to get callbacks from the UI.
+     */
+    public interface ProviderIntentControllerCallback {
+        /** Called when the user makes a selection. */
+        void onProviderIntentResult(Bundle resultData);
+        /** Called when the user cancels the UI. */
+        void onProviderIntentCancelled();
+    }
+
+    private final int mUserId;
+    private final Context mContext;
+    private final ProviderIntentControllerCallback mCallback;
+    private final ResultReceiver mResultReceiver = new ResultReceiver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            Log.i(TAG, "onReceiveResult in providerIntentController");
+
+            if (resultCode == Activity.RESULT_OK) {
+                Log.i(TAG, "onReceiveResult - ACTIVITYOK");
+                mCallback.onProviderIntentResult(resultData);
+            } else if (resultCode == Activity.RESULT_CANCELED) {
+                Log.i(TAG, "onReceiveResult - RESULTCANCELED");
+                mCallback.onProviderIntentCancelled();
+            }
+            // Drop unknown result
+        }
+    };
+
+    public ProviderIntentController(@UserIdInt int userId,
+            Context context,
+            ProviderIntentControllerCallback callback) {
+        mUserId = userId;
+        mContext = context;
+        mCallback = callback;
+    }
+
+    /** Sets up the request data and invokes the given pending intent. */
+    public void setupAndInvokePendingIntent(@NonNull PendingIntent pendingIntent,
+            CreateCredentialRequest request) {
+        Log.i(TAG, "in invokePendingIntent");
+        setupIntent(pendingIntent, request);
+        Log.i(TAG, "in invokePendingIntent receiver set up");
+        Log.i(TAG, "creator package: " + pendingIntent.getIntentSender()
+                .getCreatorPackage());
+
+        try {
+            mContext.startIntentSender(pendingIntent.getIntentSender(),
+                    null, 0, 0, 0);
+        } catch (IntentSender.SendIntentException e) {
+            Log.i(TAG, "Error while invoking pending intent");
+        }
+
+    }
+
+    private void setupIntent(PendingIntent pendingIntent, CreateCredentialRequest request) {
+        pendingIntent.getIntent().putExtra(Intent.EXTRA_RESULT_RECEIVER,
+                toIpcFriendlyResultReceiver(mResultReceiver));
+        pendingIntent.getIntent().putExtra(
+                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+                request.getData());
+    }
+
+    private <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
+            T resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 3a9f964..14a9157 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -17,25 +17,70 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
 import android.credentials.ui.ProviderData;
+import android.os.Bundle;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderInfo;
 
+import java.util.UUID;
+
 /**
  * Provider session storing the state of provider response and ui entries.
- * @param <T> The request type expected from the remote provider, for a given request session.
+ * @param <T> The request to be sent to the provider
+ * @param <R> The response to be expected from the provider
  */
-public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
+public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R>,
+        ProviderIntentController.ProviderIntentControllerCallback {
     // Key to be used as the entry key for an action entry
     protected static final String ACTION_ENTRY_KEY = "action_key";
 
+    @NonNull protected final Context mContext;
     @NonNull protected final ComponentName mComponentName;
     @NonNull protected final CredentialProviderInfo mProviderInfo;
     @NonNull protected final RemoteCredentialService mRemoteCredentialService;
     @NonNull protected final int mUserId;
     @NonNull protected Status mStatus = Status.NOT_STARTED;
     @NonNull protected final ProviderInternalCallback mCallbacks;
+    @NonNull protected final ProviderIntentController mProviderIntentController;
+    @Nullable protected Credential mFinalCredentialResponse;
+    @NonNull protected final T mProviderRequest;
+    @Nullable protected R mProviderResponse;
+
+    /**
+     * Returns true if the given status reflects that the provider state is ready to be shown
+     * on the credMan UI.
+     */
+    public static boolean isUiInvokingStatus(Status status) {
+        return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
+    }
+
+    /**
+     * Returns true if the given status reflects that the provider is waiting for a remote
+     * response.
+     */
+    public static boolean isStatusWaitingForRemoteResponse(Status status) {
+        return status == Status.PENDING;
+    }
+
+    /**
+     * Returns true if the given status means that the provider session must be terminated.
+     */
+    public static boolean isTerminatingStatus(Status status) {
+        return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+    }
+
+    /**
+     * Returns true if the given status reflects that the provider is done getting the response,
+     * and is ready to return the final credential back to the user.
+     */
+    public static boolean isCompletionStatus(Status status) {
+        return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
+                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+    }
 
     /**
      * Interface to be implemented by any class that wishes to get a callback when a particular
@@ -49,35 +94,49 @@
         void onProviderStatusChanged(Status status, ComponentName componentName);
     }
 
-    protected ProviderSession(@NonNull CredentialProviderInfo info,
+    protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+            @NonNull T providerRequest,
             @NonNull ProviderInternalCallback callbacks,
             @NonNull int userId,
             @NonNull RemoteCredentialService remoteCredentialService) {
+        mContext = context;
         mProviderInfo = info;
+        mProviderRequest = providerRequest;
         mCallbacks = callbacks;
         mUserId = userId;
         mComponentName = info.getServiceInfo().getComponentName();
         mRemoteCredentialService = remoteCredentialService;
+        mProviderIntentController = new ProviderIntentController(userId, context, this);
     }
 
-    /** Update the response state stored with the provider session. */
-    protected abstract void updateResponse (T response);
-
-    /** Update the response state stored with the provider session. */
-    protected abstract T getResponse ();
-
-    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
-     * shown on the UI. */
-    protected abstract ProviderData prepareUiData();
-
     /** Provider status at various states of the request session. */
+    // TODO: Review status values, and adjust where needed
     enum Status {
         NOT_STARTED,
         PENDING,
         REQUIRES_AUTHENTICATION,
-        COMPLETE,
+        CREDENTIALS_RECEIVED,
         SERVICE_DEAD,
-        CANCELED
+        CREDENTIAL_RECEIVED_FROM_INTENT,
+        PENDING_INTENT_INVOKED,
+        CREDENTIAL_RECEIVED_FROM_SELECTION,
+        SAVE_ENTRIES_RECEIVED, CANCELED
+    }
+
+    /** Converts exception to a provider session status. */
+    @NonNull
+    public static Status toStatus(
+            @CredentialProviderException.CredentialProviderError int errorCode) {
+        // TODO : Add more mappings as more flows are supported
+        return Status.CANCELED;
+    }
+
+    protected String generateEntryId() {
+        return UUID.randomUUID().toString();
+    }
+
+    public Credential getFinalCredentialResponse() {
+        return  mFinalCredentialResponse;
     }
 
     protected void setStatus(@NonNull Status status) {
@@ -94,31 +153,38 @@
         return mComponentName;
     }
 
+    @NonNull
+    protected RemoteCredentialService getRemoteCredentialService() {
+        return mRemoteCredentialService;
+    }
+
     /** Updates the status .*/
     protected void updateStatusAndInvokeCallback(@NonNull Status status) {
         setStatus(status);
         mCallbacks.onProviderStatusChanged(status, mComponentName);
     }
 
-    @NonNull
-    public static Status toStatus(
-            @CredentialProviderException.CredentialProviderError int errorCode) {
-        // TODO : Add more mappings as more flows are supported
-        return Status.CANCELED;
+    /** Get the request to be sent to the provider. */
+    protected T getProviderRequest() {
+        return mProviderRequest;
     }
 
-    /**
-     * Returns true if the given status means that the provider session must be terminated.
-     */
-    public static boolean isTerminatingStatus(Status status) {
-        return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+    /** Update the response state stored with the provider session. */
+    @Nullable protected R getProviderResponse() {
+        return mProviderResponse;
     }
 
-    /**
-     * Returns true if the given status means that the provider is done getting the response,
-     * and is ready for user interaction.
-     */
-    public static boolean isCompletionStatus(Status status) {
-        return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
-    }
+    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+     * shown on the UI. */
+    @Nullable protected abstract ProviderData prepareUiData();
+
+    /** Should be overridden to handle the selected entry from the UI. */
+    protected abstract void onUiEntrySelected(String entryType, String entryId);
+
+    @Override
+    public abstract void onProviderIntentResult(Bundle resultData);
+
+    @Override
+    public abstract void onProviderIntentCancelled();
+
 }
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index d0b6e7d..c2464b5 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -24,11 +24,14 @@
 import android.os.Handler;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.CreateCredentialResponse;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderException.CredentialProviderError;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.GetCredentialsRequest;
 import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.ICreateCredentialCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.service.credentials.IGetCredentialsCallback;
 import android.text.format.DateUtils;
@@ -76,7 +79,7 @@
     public RemoteCredentialService(@NonNull Context context,
             @NonNull ComponentName componentName, int userId) {
         super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
-                        .setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+                        .setComponent(componentName), /*bindingFlags=*/0,
                 userId, ICredentialProviderService.Stub::asInterface);
         mComponentName = componentName;
     }
@@ -101,7 +104,7 @@
      * provider service.
      * @param request the request to be sent to the provider
      * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderSession} class that maintains provider state
+     *                 {@link ProviderGetSession} class that maintains provider state
      */
     public void onGetCredentials(@NonNull GetCredentialsRequest request,
             ProviderCallbacks<GetCredentialsResponse> callback) {
@@ -114,21 +117,21 @@
             CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
             ICancellationSignal cancellationSignal =
                     service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
-                @Override
-                public void onSuccess(GetCredentialsResponse response) {
-                    Log.i(TAG, "In onSuccess in RemoteCredentialService");
-                    getCredentials.complete(response);
-                }
+                        @Override
+                        public void onSuccess(GetCredentialsResponse response) {
+                            Log.i(TAG, "In onSuccess in RemoteCredentialService");
+                            getCredentials.complete(response);
+                        }
 
-                @Override
-                public void onFailure(@CredentialProviderError int errorCode,
-                        CharSequence message) {
-                    Log.i(TAG, "In onFailure in RemoteCredentialService");
-                    String errorMsg = message == null ? "" : String.valueOf(message);
-                    getCredentials.completeExceptionally(new CredentialProviderException(
-                            errorCode, errorMsg));
-                }
-            });
+                        @Override
+                        public void onFailure(@CredentialProviderError int errorCode,
+                                CharSequence message) {
+                            Log.i(TAG, "In onFailure in RemoteCredentialService");
+                            String errorMsg = message == null ? "" : String.valueOf(message);
+                            getCredentials.completeExceptionally(new CredentialProviderException(
+                                    errorCode, errorMsg));
+                        }
+                    });
             CompletableFuture<GetCredentialsResponse> future = futureRef.get();
             if (future != null && future.isCancelled()) {
                 dispatchCancellationSignal(cancellationSignal);
@@ -137,38 +140,91 @@
             }
             return getCredentials;
         }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
-        futureRef.set(connectThenExecute);
 
-        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
-            if (error == null) {
-                Log.i(TAG, "In RemoteCredentialService execute error is null");
-                callback.onProviderResponseSuccess(result);
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
+    /** Main entry point to be called for executing a createCredential call on the remote
+     * provider service.
+     * @param request the request to be sent to the provider
+     * @param callback the callback to be used to send back the provider response to the
+     *                 {@link ProviderCreateSession} class that maintains provider state
+     */
+    public void onCreateCredential(@NonNull CreateCredentialRequest request,
+            ProviderCallbacks<CreateCredentialResponse> callback) {
+        Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<CreateCredentialResponse>> futureRef =
+                new AtomicReference<>();
+
+        CompletableFuture<CreateCredentialResponse> connectThenExecute = postAsync(service -> {
+            CompletableFuture<CreateCredentialResponse> createCredentialFuture =
+                    new CompletableFuture<>();
+            ICancellationSignal cancellationSignal = service.onCreateCredential(
+                    request, new ICreateCredentialCallback.Stub() {
+                        @Override
+                        public void onSuccess(CreateCredentialResponse response) {
+                            Log.i(TAG, "In onSuccess onCreateCredential "
+                                    + "in RemoteCredentialService");
+                            createCredentialFuture.complete(response);
+                        }
+
+                        @Override
+                        public void onFailure(@CredentialProviderError int errorCode,
+                                CharSequence message) {
+                            Log.i(TAG, "In onFailure in RemoteCredentialService");
+                            String errorMsg = message == null ? "" : String.valueOf(message);
+                            createCredentialFuture.completeExceptionally(
+                                    new CredentialProviderException(errorCode, errorMsg));
+                        }});
+            CompletableFuture<CreateCredentialResponse> future = futureRef.get();
+            if (future != null && future.isCancelled()) {
+                dispatchCancellationSignal(cancellationSignal);
             } else {
-                if (error instanceof TimeoutException) {
-                    Log.i(TAG, "In RemoteCredentialService execute error is timeout");
-                    dispatchCancellationSignal(cancellationSink.get());
-                    callback.onProviderResponseFailure(
-                            CredentialProviderException.ERROR_TIMEOUT,
-                            error.getMessage());
-                } else if (error instanceof CancellationException) {
-                    Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
-                    dispatchCancellationSignal(cancellationSink.get());
-                    callback.onProviderResponseFailure(
-                            CredentialProviderException.ERROR_TASK_CANCELED,
-                            error.getMessage());
-                } else if (error instanceof CredentialProviderException) {
-                    Log.i(TAG, "In RemoteCredentialService execute error is provider error");
-                    callback.onProviderResponseFailure(((CredentialProviderException) error)
-                                    .getErrorCode(),
-                            error.getMessage());
-                } else {
-                    Log.i(TAG, "In RemoteCredentialService execute error is unknown");
-                    callback.onProviderResponseFailure(
-                            CredentialProviderException.ERROR_UNKNOWN,
-                            error.getMessage());
-                }
+                cancellationSink.set(cancellationSignal);
             }
-        }));
+            return createCredentialFuture;
+        }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
+    private <T> void handleExecutionResponse(T result,
+            Throwable error,
+            AtomicReference<ICancellationSignal> cancellationSink,
+            ProviderCallbacks<T> callback) {
+        if (error == null) {
+            Log.i(TAG, "In RemoteCredentialService execute error is null");
+            callback.onProviderResponseSuccess(result);
+        } else {
+            if (error instanceof TimeoutException) {
+                Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+                dispatchCancellationSignal(cancellationSink.get());
+                callback.onProviderResponseFailure(
+                        CredentialProviderException.ERROR_TIMEOUT,
+                        error.getMessage());
+            } else if (error instanceof CancellationException) {
+                Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+                dispatchCancellationSignal(cancellationSink.get());
+                callback.onProviderResponseFailure(
+                        CredentialProviderException.ERROR_TASK_CANCELED,
+                        error.getMessage());
+            } else if (error instanceof CredentialProviderException) {
+                Log.i(TAG, "In RemoteCredentialService execute error is provider error");
+                callback.onProviderResponseFailure(((CredentialProviderException) error)
+                                .getErrorCode(),
+                        error.getMessage());
+            } else {
+                Log.i(TAG, "In RemoteCredentialService execute error is unknown");
+                callback.onProviderResponseFailure(
+                        CredentialProviderException.ERROR_UNKNOWN,
+                        error.getMessage());
+            }
+        }
     }
 
     private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 1bacbb3..056d0e8 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -20,18 +20,30 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
+import android.credentials.ui.ProviderData;
 import android.credentials.ui.UserSelectionDialogResult;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Base class of a request session, that listens to UI events. This class must be extended
  * every time a new response type is expected from the providers.
  */
-abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback,
         ProviderSession.ProviderInternalCallback {
+    private static final String TAG = "RequestSession";
+
+    // TODO: Revise access levels of attributes
+    @NonNull protected final T mClientRequest;
+    @NonNull protected final U mClientCallback;
     @NonNull protected final IBinder mRequestId;
     @NonNull protected final Context mContext;
     @NonNull protected final CredentialManagerUi mCredentialManagerUi;
@@ -39,29 +51,120 @@
     @NonNull protected final Handler mHandler;
     @NonNull protected boolean mIsFirstUiTurn = true;
     @UserIdInt protected final int mUserId;
+    @NonNull protected final String mClientCallingPackage;
+
+    protected final Map<String, ProviderSession> mProviders = new HashMap<>();
 
     protected RequestSession(@NonNull Context context,
-            @UserIdInt int userId, @NonNull String requestType) {
+            @UserIdInt int userId, @NonNull T clientRequest, U clientCallback,
+            @NonNull String requestType,
+            String clientCallingPackage) {
         mContext = context;
         mUserId = userId;
+        mClientRequest = clientRequest;
+        mClientCallback = clientCallback;
         mRequestType = requestType;
+        mClientCallingPackage = clientCallingPackage;
         mHandler = new Handler(Looper.getMainLooper(), null, true);
         mRequestId = new Binder();
         mCredentialManagerUi = new CredentialManagerUi(mContext,
                 mUserId, this);
     }
 
-    /** Returns the unique identifier of this request session. */
-    public IBinder getRequestId() {
-        return mRequestId;
+    public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService);
+
+    protected abstract void launchUiWithProviderData(ArrayList<ProviderData> providerDataList);
+
+    // UI callbacks
+
+    @Override // from CredentialManagerUiCallbacks
+    public void onUiSelection(UserSelectionDialogResult selection) {
+        String providerId = selection.getProviderId();
+        Log.i(TAG, "onUiSelection, providerId: " + providerId);
+        ProviderSession providerSession = mProviders.get(providerId);
+        if (providerSession == null) {
+            Log.i(TAG, "providerSession not found in onUiSelection");
+            return;
+        }
+        Log.i(TAG, "Provider session found");
+        providerSession.onUiEntrySelected(selection.getEntryKey(),
+                selection.getEntrySubkey());
     }
 
-    @Override // from CredentialManagerUiCallback
-    public abstract void onUiSelection(UserSelectionDialogResult selection);
+    @Override // from CredentialManagerUiCallbacks
+    public void onUiCancellation() {
+        // User canceled the activity
+        finishSession();
+    }
 
-    @Override // from CredentialManagerUiCallback
-    public abstract void onUiCancelation();
+    @Override // from provider session
+    public void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        Log.i(TAG, "in onStatusChanged with status: " + status);
+        if (ProviderSession.isTerminatingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged terminating status");
+            onProviderTerminated(componentName);
+            //TODO: Check if this was the provider we were waiting for and can invoke the UI now
+        } else if (ProviderSession.isCompletionStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            onProviderResponseComplete(componentName);
+        } else if (ProviderSession.isUiInvokingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isUiInvokingStatus status");
+            onProviderResponseRequiresUi();
+        }
+    }
 
-    @Override // from ProviderInternalCallback
-    public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+    protected void onProviderTerminated(ComponentName componentName) {
+        //TODO: Implement
+    }
+
+    protected void onProviderResponseComplete(ComponentName componentName) {
+        //TODO: Implement
+    }
+
+    protected void onProviderResponseRequiresUi() {
+        Log.i(TAG, "in onProviderResponseComplete");
+        // TODO: Determine whether UI has already been invoked, and deal accordingly
+        if (!isAnyProviderPending()) {
+            Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+            getProviderDataAndInitiateUi();
+        } else {
+            Log.i(TAG, "Can't invoke UI - waiting on some providers");
+        }
+    }
+
+    protected void finishSession() {
+        clearProviderSessions();
+    }
+
+    protected void clearProviderSessions() {
+        //TODO: Implement
+        mProviders.clear();
+    }
+
+    private boolean isAnyProviderPending() {
+        for (ProviderSession session : mProviders.values()) {
+            if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void getProviderDataAndInitiateUi() {
+        ArrayList<ProviderData> providerDataList = new ArrayList<>();
+        for (ProviderSession session : mProviders.values()) {
+            Log.i(TAG, "preparing data for : " + session.getComponentName());
+            ProviderData providerData = session.prepareUiData();
+            if (providerData != null) {
+                Log.i(TAG, "Provider data is not null");
+                providerDataList.add(providerData);
+            }
+        }
+        if (!providerDataList.isEmpty()) {
+            Log.i(TAG, "provider list not empty about to initiate ui");
+            launchUiWithProviderData(providerDataList);
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 316c736..89cbf53 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -646,6 +646,15 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long USE_SET_LOCATION_ENABLED = 117835097L;
 
+    /**
+     * Forces wipeDataNoLock to attempt removing the user or throw an error as
+     * opposed to trying to factory reset the device first and only then falling back to user
+     * removal.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L;
+
     // Only add to the end of the list. Do not change or rearrange these values, that will break
     // historical data. Do not use negative numbers or zero, logger only handles positive
     // integers.
@@ -6699,8 +6708,8 @@
     }
 
     @Override
-    public void wipeDataWithReason(int flags, String wipeReasonForUser,
-            boolean calledOnParentInstance) {
+    public void wipeDataWithReason(int flags, @NonNull String wipeReasonForUser,
+            boolean calledOnParentInstance, boolean factoryReset) {
         if (!mHasFeature && !hasCallingOrSelfPermission(permission.MASTER_CLEAR)) {
             return;
         }
@@ -6782,7 +6791,8 @@
                 "DevicePolicyManager.wipeDataWithReason() from %s, organization-owned? %s",
                 adminName, calledByProfileOwnerOnOrgOwnedDevice);
 
-        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
+        wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId,
+                calledOnParentInstance, factoryReset);
     }
 
     private String getGenericWipeReason(
@@ -6844,8 +6854,13 @@
         Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
     }
 
+    /**
+     * @param factoryReset null: legacy behaviour, false: attempt to remove user, true: attempt to
+     *                     factory reset
+     */
     private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
-                                String wipeReasonForUser, int userId) {
+            @NonNull String wipeReasonForUser, int userId, boolean calledOnParentInstance,
+            @Nullable Boolean factoryReset) {
         wtfIfInLock();
 
         mInjector.binderWithCleanCallingIdentity(() -> {
@@ -6863,7 +6878,37 @@
                         + " restriction is set for user " + userId);
             }
 
-            if (userId == UserHandle.USER_SYSTEM) {
+            boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+            boolean wipeDevice;
+            if (factoryReset == null || !CompatChanges.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR)) {
+                // Legacy mode
+                wipeDevice = isSystemUser;
+            } else {
+                // Explicit behaviour
+                if (factoryReset) {
+                    // TODO(b/254031494) Replace with new factory reset permission checks
+                    boolean hasPermission = isDeviceOwnerUserId(userId)
+                            || (isOrganizationOwnedDeviceWithManagedProfile()
+                            && calledOnParentInstance);
+                    Preconditions.checkState(hasPermission,
+                            "Admin %s does not have permission to factory reset the device.",
+                            userId);
+                    wipeDevice = true;
+                } else {
+                    Preconditions.checkCallAuthorization(!isSystemUser,
+                            "User %s is a system user and cannot be removed", userId);
+                    boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
+                            && mUserManager.getAliveUsers().stream()
+                            .filter((it) -> it.getUserHandle().getIdentifier() != userId)
+                            .noneMatch(UserInfo::isFull);
+                    Preconditions.checkState(!isLastNonHeadlessUser,
+                            "Removing user %s would leave the device without any active users. "
+                                    + "Consider factory resetting the device instead.",
+                            userId);
+                    wipeDevice = false;
+                }
+            }
+            if (wipeDevice) {
                 forceWipeDeviceNoLock(
                         (flags & WIPE_EXTERNAL_STORAGE) != 0,
                         internalReason,
@@ -7131,7 +7176,7 @@
     }
 
     @Override
-    public void reportFailedPasswordAttempt(int userHandle) {
+    public void reportFailedPasswordAttempt(int userHandle, boolean parent) {
         Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
 
         final CallerIdentity caller = getCallerIdentity();
@@ -7153,7 +7198,7 @@
                 saveSettingsLocked(userHandle);
                 if (mHasFeature) {
                     strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
-                            userHandle, /* parent */ false);
+                            userHandle, /* parent= */ false);
                     int max = strictestAdmin != null
                             ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                     if (max > 0 && policy.mFailedPasswordAttempts >= max) {
@@ -7183,10 +7228,13 @@
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
-                        /*flags=*/ 0,
-                        /*reason=*/ "reportFailedPasswordAttempt()",
+                        /* flags= */ 0,
+                        /* reason= */ "reportFailedPasswordAttempt()",
                         getFailedPasswordAttemptWipeMessage(),
-                        userId);
+                        userId,
+                        /* calledOnParentInstance= */ parent,
+                        // factoryReset=null to enable U- behaviour
+                        /* factoryReset= */ null);
             } catch (SecurityException e) {
                 Slogf.w(LOG_TAG, "Failed to wipe user " + userId
                         + " after max failed password attempts reached.", e);
@@ -7195,7 +7243,7 @@
 
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
-                    /*result*/ 0, /*method strength*/ 1);
+                    /* result= */ 0, /* method strength= */ 1);
         }
     }
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3fbc400..640bde3 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -244,7 +244,7 @@
                     .setCurrentMethodVisible();
         }
         verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
-                .showSoftInput(any(), anyInt(), any());
+                .showSoftInput(any(), any(), anyInt(), any());
     }
 
     protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
@@ -254,6 +254,6 @@
                     .setCurrentMethodNotVisible();
         }
         verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
-                .hideSoftInput(any(), anyInt(), any());
+                .hideSoftInput(any(), any(), anyInt(), any());
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index e1713b0..98e895a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -22,6 +22,7 @@
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
 import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_WIFI_SCAN;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
@@ -127,6 +128,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -137,6 +140,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -151,6 +156,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
@@ -169,6 +176,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -183,6 +192,8 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -197,6 +208,8 @@
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -211,6 +224,8 @@
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -314,6 +329,8 @@
                 .update();
 
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -328,6 +345,8 @@
                 .update();
 
         assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+        assertEquals(MODE_IGNORED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -403,6 +422,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -418,6 +439,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
@@ -433,6 +456,8 @@
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
         assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+        assertEquals(MODE_ALLOWED,
+                mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
     }
 
     @Test
diff --git a/services/tests/servicestests/res/xml/usertypes_test_full.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
index 099ccbe..9568143 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_full.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -16,7 +16,8 @@
 <user-types>
     <full-type
         name='android.test.1'
-        max-allowed-per-parent='12' >
+        max-allowed-per-parent='12'
+        max-allowed='17' >
         <default-restrictions no_remove_user='true' no_bluetooth='true' />
         <badge-colors>
             <item res='@*android:color/profile_badge_1' />
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index b27f49d..1a6dae37 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -33,6 +33,7 @@
         <user-properties
             showInLauncher='2020'
             startWithParent='false'
+            useParentsContacts='false'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 4c939f0..0262f56 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -82,6 +82,7 @@
                 /* blockedActivities= */ new ArraySet<>(),
                 VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
                 /* activityListener= */ null,
+                /* pipBlockedCallback= */ null,
                 /* activityBlockedCallback= */ null,
                 /* secureWindowCallback= */ null,
                 /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ddb3049..8e669f0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -140,7 +140,7 @@
 import android.security.keystore.AttestationUtils;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.test.MoreAsserts; // TODO(b/171932723): replace by Truth
+import android.test.MoreAsserts;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -5087,7 +5087,7 @@
     }
 
     @Test
-    public void testWipeDataDeviceOwner() throws Exception {
+    public void testWipeDevice_DeviceOwner() throws Exception {
         setDeviceOwner();
         when(getServices().userManager.getUserRestrictionSource(
                 UserManager.DISALLOW_FACTORY_RESET,
@@ -5096,7 +5096,7 @@
         when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                 thenReturn("Just a test string.");
 
-        dpm.wipeData(0);
+        dpm.wipeDevice(0);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ false);
     }
@@ -5111,13 +5111,13 @@
         when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
                 thenReturn("Just a test string.");
 
-        dpm.wipeData(WIPE_EUICC);
+        dpm.wipeDevice(WIPE_EUICC);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ true);
     }
 
     @Test
-    public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+    public void testWipeDevice_DeviceOwnerDisallowed() throws Exception {
         setDeviceOwner();
         when(getServices().userManager.getUserRestrictionSource(
                 UserManager.DISALLOW_FACTORY_RESET,
@@ -5128,7 +5128,7 @@
         // The DO is not allowed to wipe the device if the user restriction was set
         // by the system
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
-                () -> dpm.wipeData(0));
+                () -> dpm.wipeDevice(0));
     }
 
     @Test
@@ -7986,7 +7986,7 @@
     }
 
     @Test
-    public void testWipeData_financeDo_success() throws Exception {
+    public void testWipeDevice_financeDo_success() throws Exception {
         setDeviceOwner();
         dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
         when(getServices().userManager.getUserRestrictionSource(
@@ -7997,7 +7997,7 @@
                 .getString(R.string.work_profile_deleted_description_dpm_wipe))
                 .thenReturn("Test string");
 
-        dpm.wipeData(0);
+        dpm.wipeDevice(0);
 
         verifyRebootWipeUserData(/* wipeEuicc= */ false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index 9c8e72c..f5029ec 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -71,7 +71,7 @@
     private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
     private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
     private static final long POLL_INTERVAL = 500;
-    private static final long DEFAULT_WAIT_TIMEOUT = 5000;
+    private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
 
     private Context mContext;
     private AppOpsManager mAppOpsManager;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 5ece871..1f952c4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -61,11 +61,13 @@
                 .setStartWithParent(false)
                 .setShowInSettings(45)
                 .setInheritDevicePolicy(67)
+                .setUseParentsContacts(false)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
         actualProps.setShowInSettings(32);
         actualProps.setInheritDevicePolicy(51);
+        actualProps.setUseParentsContacts(true);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -152,11 +154,14 @@
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
                 hasManagePermission);
+        assertEqualGetterOrThrows(orig::getUseParentsContacts,
+                copy::getUseParentsContacts, hasManagePermission);
 
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
         // Items with no permission requirements.
         assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+
     }
 
     /**
@@ -196,7 +201,7 @@
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
         assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
-        assertThat(expected.getInheritDevicePolicy()).isEqualTo(
-                actual.getInheritDevicePolicy());
+        assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy());
+        assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 5f48004..d7c1e37 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -83,7 +83,8 @@
                 /* flags= */0,
                 /* letsPersonalDataIntoProfile= */false).build());
         final UserProperties.Builder userProps = new UserProperties.Builder()
-                .setShowInLauncher(17);
+                .setShowInLauncher(17)
+                .setUseParentsContacts(true);
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(1)
@@ -140,6 +141,7 @@
         }
 
         assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+        assertTrue(type.getDefaultUserPropertiesReference().getUseParentsContacts());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -182,6 +184,7 @@
         final UserProperties props = type.getDefaultUserPropertiesReference();
         assertNotNull(props);
         assertFalse(props.getStartWithParent());
+        assertFalse(props.getUseParentsContacts());
         assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
 
         assertFalse(type.hasBadge());
@@ -263,7 +266,8 @@
         final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
         final UserProperties.Builder props = new UserProperties.Builder()
                 .setShowInLauncher(19)
-                .setStartWithParent(true);
+                .setStartWithParent(true)
+                .setUseParentsContacts(true);
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
                 .setName(userTypeAosp1)
@@ -289,7 +293,9 @@
         assertEquals(Resources.ID_NULL, aospType.getIconBadge());
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
         assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
-        assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference()
+                .getUseParentsContacts());
 
         // userTypeAosp2 should be modified.
         aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -319,7 +325,9 @@
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 aospType.getDefaultRestrictions()));
         assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
-        assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference()
+                .getUseParentsContacts());
 
         // userTypeOem1 should be created.
         UserTypeDetails.Builder customType = builders.get(userTypeOem1);
@@ -347,6 +355,7 @@
         UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
         assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
         assertFalse(details.isEnabled());
+        assertEquals(17, details.getMaxAllowed());
         assertTrue(UserRestrictionsUtils.areEqual(
                 makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
                 details.getDefaultRestrictions()));
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 a3c45b7..2e7e583 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
@@ -164,6 +165,14 @@
 
     @Test
     public void testCloneUser() throws Exception {
+
+        // Get the default properties for clone user type.
+        final UserTypeDetails userTypeDetails =
+                UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_CLONE);
+        assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_CLONE)
+                .that(userTypeDetails).isNotNull();
+        final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
         // Test that only one clone user can be created
         final int primaryUserId = mUserManager.getPrimaryUser().id;
         UserInfo userInfo = createProfileForUser("Clone user1",
@@ -187,6 +196,16 @@
                 .collect(Collectors.toList());
         assertThat(cloneUsers.size()).isEqualTo(1);
 
+        // Check that the new clone user has the expected properties (relative to the defaults)
+        // provided that the test caller has the necessary permissions.
+        UserProperties cloneUserProperties =
+                mUserManager.getUserProperties(UserHandle.of(userInfo.id));
+        assertThat(typeProps.getUseParentsContacts())
+                .isEqualTo(cloneUserProperties.getUseParentsContacts());
+        assertThat(typeProps.getShowInLauncher())
+                .isEqualTo(cloneUserProperties.getShowInLauncher());
+        assertThrows(SecurityException.class, cloneUserProperties::getStartWithParent);
+
         // Verify clone user parent
         assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
         UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
@@ -600,6 +619,7 @@
         // provided that the test caller has the necessary permissions.
         assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
         assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
+        assertFalse(userProps.getUseParentsContacts());
         assertThrows(SecurityException.class, userProps::getStartWithParent);
         assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
     }
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
index 3e79407..b8585f2 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
@@ -34,7 +34,8 @@
     public boolean onStartJob(JobParameters params) {
         Log.i(TAG, "Test job executing: " + params.getJobId());
         Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
-        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sendBroadcast(reportJobStartIntent);
         return true;
     }
@@ -43,7 +44,8 @@
     public boolean onStopJob(JobParameters params) {
         Log.i(TAG, "Test job stopped executing: " + params.getJobId());
         Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
-        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sendBroadcast(reportJobStopIntent);
         // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
         return false;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 376399a..85c4975 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -324,7 +324,7 @@
 
         // The activity reports fully drawn before windows drawn, then the fully drawn event will
         // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
-        mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+        mActivityMetricsLogger.notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
         notifyTransitionStarting(mTopActivity);
         // The pending fully drawn event should send when the actual windows drawn event occurs.
         final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
@@ -337,7 +337,7 @@
         verifyNoMoreInteractions(mLaunchObserver);
 
         final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
-                .logAppTransitionReportedDrawn(mTopActivity, false /* restoredFromBundle */);
+                .notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
         assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3a8e1cc..bd8da4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2284,8 +2284,7 @@
         doReturn(false).when(mAtm).shouldDisableNonVrUiLocked();
 
         spyOn(mDisplayContent.mDwpcHelper);
-        doReturn(false).when(mDisplayContent.mDwpcHelper).isWindowingModeSupported(
-                WINDOWING_MODE_PINNED);
+        doReturn(false).when(mDisplayContent.mDwpcHelper).isEnteringPipAllowed(anyInt());
 
         assertFalse(activity.checkEnterPictureInPictureState("TEST", false /* beforeStopping */));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 52af8ad..d99946f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -45,6 +45,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -375,10 +376,10 @@
 
         displayPolicy.setCanSystemBarsBeShownByUser(false);
         displayPolicy.requestTransientBars(windowState, true);
-        verify(controlTarget, never()).showInsets(anyInt(), anyBoolean());
+        verify(controlTarget, never()).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
 
         displayPolicy.setCanSystemBarsBeShownByUser(true);
         displayPolicy.requestTransientBars(windowState, true);
-        verify(controlTarget).showInsets(anyInt(), anyBoolean());
+        verify(controlTarget).showInsets(anyInt(), anyBoolean(), any() /* statsToken */);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index 21197ba..db1d15a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -246,5 +246,10 @@
         public boolean canShowTasksInRecents() {
             return true;
         }
+
+        @Override
+        public boolean isEnteringPipAllowed(int uid) {
+            return true;
+        }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index eb8b89d..a26cad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -55,7 +55,7 @@
         mDisplayContent.setImeControlTarget(popup);
         mDisplayContent.setImeLayeringTarget(appWin);
         popup.mAttrs.format = PixelFormat.TRANSPARENT;
-        mImeProvider.scheduleShowImePostLayout(appWin);
+        mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */);
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -64,7 +64,7 @@
         WindowState target = createWindow(null, TYPE_APPLICATION, "app");
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.updateImeInputAndControlTarget(target);
-        mImeProvider.scheduleShowImePostLayout(target);
+        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
         assertTrue(mImeProvider.isReadyToShowIme());
     }
 
@@ -78,7 +78,7 @@
         mDisplayContent.setImeLayeringTarget(target);
         mDisplayContent.setImeControlTarget(target);
 
-        mImeProvider.scheduleShowImePostLayout(target);
+        mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */);
         assertFalse(mImeProvider.isImeShowing());
         mImeProvider.checkShowImePostLayout();
         assertTrue(mImeProvider.isImeShowing());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index bb5aceb..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -26,6 +27,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.ScrollCaptureResponse;
+import android.view.inputmethod.ImeTracker;
 import android.window.ClientWindowFrames;
 
 import com.android.internal.os.IResultReceiver;
@@ -117,10 +119,12 @@
     }
 
     @Override
-    public void showInsets(int types, boolean fromIme) throws RemoteException {
+    public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+            throws RemoteException {
     }
 
     @Override
-    public void hideInsets(int types, boolean fromIme) throws RemoteException {
+    public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)
+            throws RemoteException {
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0139f6a..6bd3412 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1000,7 +1000,7 @@
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.setImeInputTarget(app);
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
@@ -1037,7 +1037,7 @@
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.setImeInputTarget(app);
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
+        controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */);
         controller.getImeSourceProvider().getSource().setVisible(true);
         controller.updateAboveInsetsState(false);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index eca7cbb..ab042d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -100,6 +100,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
+import android.view.inputmethod.ImeTracker;
 import android.window.ITransitionPlayer;
 import android.window.ScreenCapture;
 import android.window.StartingWindowInfo;
@@ -848,11 +849,13 @@
             }
 
             @Override
-            public void showInsets(int i, boolean b) throws RemoteException {
+            public void showInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+                    throws RemoteException {
             }
 
             @Override
-            public void hideInsets(int i, boolean b) throws RemoteException {
+            public void hideInsets(int i, boolean b, @Nullable ImeTracker.Token t)
+                    throws RemoteException {
             }
 
             @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 1fc54d6..0721c28 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -206,7 +205,7 @@
     @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
 
     final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
-            this::setProximityMeters;
+            this::setProximityValue;
 
 
     volatile HotwordDetectionServiceIdentity mIdentity;
@@ -504,7 +503,7 @@
                         mSoftwareCallback.onError();
                         return;
                     }
-                    saveProximityMetersToBundle(result);
+                    saveProximityValueToBundle(result);
                     HotwordDetectedResult newResult;
                     try {
                         newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -639,7 +638,7 @@
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
-                    saveProximityMetersToBundle(result);
+                    saveProximityValueToBundle(result);
                     HotwordDetectedResult newResult;
                     try {
                         newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
@@ -1242,15 +1241,15 @@
         });
     }
 
-    private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+    private void saveProximityValueToBundle(HotwordDetectedResult result) {
         synchronized (mLock) {
             if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
-                result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+                result.setProximity(mProximityMeters);
             }
         }
     }
 
-    private void setProximityMeters(double proximityMeters) {
+    private void setProximityValue(double proximityMeters) {
         synchronized (mLock) {
             mProximityMeters = proximityMeters;
         }
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 432af3a..5cef2cb 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -168,6 +168,18 @@
     public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
 
     /**
+     * Extra key intended for {@link InCallService}s that notify the user of an incoming call. When
+     * EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB returns true, the {@link InCallService} should not
+     * interrupt the user of the incoming call because the call is being suppressed by Do Not
+     * Disturb settings.
+     *
+     * This extra will be removed from the {@link Call} object for {@link InCallService}s that do
+     * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+     */
+    public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB =
+            "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
+
+    /**
      * Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
      * UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
      * will have the same length and be in the same order as the list passed with
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index ff87ab0..a69dfb0b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -111,6 +111,8 @@
         public static final int FILTERING_INITIATED = 106;
         public static final int FILTERING_COMPLETED = 107;
         public static final int FILTERING_TIMED_OUT = 108;
+        public static final int DND_CHECK_INITIATED = 109;
+        public static final int DND_CHECK_COMPLETED = 110;
 
         public static final int SKIP_RINGING = 200;
         public static final int SILENCE = 201;
@@ -195,6 +197,7 @@
         public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
         public static final int FILTERING_COMPLETED_TIMING = 10;
         public static final int FILTERING_TIMED_OUT_TIMING = 11;
+        public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12;
         /** {@hide} */
         public static final int START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e099e69..d473c6a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17339,7 +17339,7 @@
      * Subsequent attempts will return the same error until the request is made on the default
      * data subscription.
      */
-    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14;
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION = 14;
 
     /**
      * Purchase premium capability was successful and is waiting for the network to setup the
@@ -17369,7 +17369,7 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
     public @interface PurchasePremiumCapabilityResult {}
 
@@ -17409,8 +17409,8 @@
                 return "NETWORK_NOT_AVAILABLE";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED:
                 return "ENTITLEMENT_CHECK_FAILED";
-            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB:
-                return "NOT_DEFAULT_DATA_SUB";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION:
+                return "NOT_DEFAULT_DATA_SUBSCRIPTION";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
                 return "PENDING_NETWORK_SETUP";
             default:
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index d91aa1e..a7d6a01 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -15,6 +15,8 @@
         <option name="run-command" value="cmd window tracing frame" />
         <!-- ensure lock screen mode is swipe -->
         <option name="run-command" value="locksettings set-disabled false" />
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
         <!-- restart launcher to activate TAPL -->
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
     </target_preparer>